Skip to content

Instantly share code, notes, and snippets.

@richlander
Created June 20, 2026 11:32
Show Gist options
  • Select an option

  • Save richlander/09ca856c827f974650797e9670b2eb80 to your computer and use it in GitHub Desktop.

Select an option

Save richlander/09ca856c827f974650797e9670b2eb80 to your computer and use it in GitHub Desktop.
dotnet-inspect decompiler review

dotnet-inspect decompiler review

Summary

ILInspector.Decompiler is not yet close to ILSpy-level state of the art as a general-purpose .NET decompiler. It is much closer to state of the art in observability, verification discipline, and product fit for an inspection CLI.

The core design is coherent: typed IR, an explicit ordered pass list, honest degradation, stage dumps that end on the shipped renderer, and compile-back fidelity gates. The main gap is feature throughput: many high-level C# recovery passes that mature decompilers already have are still absent or partial.

The SRM + NativeAOT-compatible constraint is not a mistake. It is the product's moat. It makes the shipped tool fast, safe to run on arbitrary packages, and dependency-light. But it also means the project must build much of the type system and metadata substrate that ILSpy gets from its richer decompiler stack.

Current position

Strong areas

  • The architecture is clean and compiler-shaped: typed IR, named passes, and a simple final printer.
  • The importer has the right safety posture: unsupported or out-of-slice IL becomes explicit diagnostics and partial fidelity instead of plausible but wrong C#.
  • The pass list is readable and maintainable. The order itself documents the pipeline.
  • The verification story is unusually strong:
    • CoreLib health floors.
    • Fixture compile-back opcode fidelity gates.
    • Stage dumps and pass diffs.
    • Validity and annotation checks.
    • An adversarial fixture workflow tied to the lowering ledger.
  • The product/harness split is right: the product projects decompiler output; the harness uses Roslyn to judge it.

Weak areas versus SOTA

  • No async/await or iterator/yield recovery yet.
  • No foreach recovery.
  • No query expression recovery.
  • No deconstruction assignment or tuple binary operator recovery.
  • Pattern/switch recovery is partial.
  • Object/collection initializer recovery is partial.
  • Range, interpolation, tuple, using, and pattern forms are partial.
  • Control-flow structuring is intentionally conservative and still leaves many flat/goto-shaped residuals.
  • Local naming without PDBs is inspection-faithful but not source-like.
  • Metadata/type facts that ILSpy gets from its type system are still being rebuilt piece by piece.

SRM and NativeAOT assessment

The SRM-only design is strategically good for dotnet-inspect.

It gives the shipped product:

  • no assembly loading;
  • no Roslyn dependency in the CLI;
  • predictable startup;
  • NativeAOT compatibility;
  • smaller dependency and attack surface;
  • offline-friendly behavior;
  • safer inspection of arbitrary packages and binaries.

That is exactly the right bias for an inspection tool.

The cost is feature velocity. ILSpy has a mature type system, AST, resolver, generated-member hiding, name collision handling, formatting, and a large suite of transforms. With SRM, this project must explicitly model each fact it needs:

  • symbolic type identity;
  • cross-assembly type/member resolution;
  • MemberRef parameter/ref-kind recovery;
  • attributes and compiler-generated classification;
  • PDB scopes, local names, tuple names, and state-machine metadata;
  • property/event/method semantic lookup;
  • nullable metadata;
  • type display and name collision handling.

The current TypeRef, MetadataSource, MetadataContext, and CrossAssemblyTypeResolver direction is right. The next step is to treat this SRM-native metadata/type substrate as a first-class platform, not incidental support code.

Recommended roadmap

1. Build the SRM-native type substrate

Before chasing every sugar feature, invest in shared metadata services that all passes can consume:

  • cached cross-assembly type/member lookup;
  • method parameter metadata for MemberRef calls;
  • generated member/type classification;
  • property/event semantic lookup;
  • PDB local-scope and state-machine helpers;
  • nullable and tuple-name metadata decoding;
  • deterministic type display and name collision services.

This is the SRM-compatible replacement for the richer infrastructure ILSpy uses.

2. Land state-machine recovery

Async/await and iterators are the biggest visible SOTA gap. Prefer a PDB-first implementation, then add shape fallback. These should be dedicated passes, not printer tricks.

3. Improve control-flow structuring

The current conservative slice is safe, but SOTA output needs better region formation:

  • stronger loop recognition;
  • better switch recovery;
  • common-exit handling;
  • reduced nesting;
  • principled irreducible fallback.

Keep the honest-goto rule, but reduce how often it triggers.

4. Add an opt-in readable naming pass

Keep V_N/S_N names for IL-aligned inspection. Add a separate readable mode that synthesizes names from type/use patterns. This would materially improve human readability without compromising the inspection view.

5. Promote the lowering ledger into a measured work queue

The ledger is already valuable. Make it drive work directly:

  • fixture counts per row;
  • positive and adversarial coverage status;
  • pass-impact data;
  • current corpus residual buckets;
  • next suggested discriminator for each partial row.

6. Expand compile-back baselines

The fixture fidelity gate is strong but narrow. Add per-method opcode snapshots for larger corpora so broad work can be reviewed as regressions and wins rather than only floor movement.

7. Prioritize validity gaps before cosmetic sugar

Any output that does not parse or bind should outrank style improvements. Validity expands the set that fidelity can judge and prevents "Full but not compilable" drift.

Product stance

Do not try to become ILSpy by importing ILSpy's product shape wholesale.

dotnet-inspect has a different and defensible niche:

  • inspection-first;
  • fast CLI;
  • NativeAOT-friendly;
  • SRM-only shipped path;
  • Roslyn kept in developer/CI oracle tooling;
  • honest degradation over aggressive guessing;
  • strong machine-readable diagnostics and stage visibility.

The right goal is not "match ILSpy feature-for-feature immediately." The right goal is "be the most trustworthy inspection decompiler for .NET packages and libraries, then grow high-level recovery behind fidelity gates."

Bottom line

The decompiler is maybe 25-35% of the way to a broad SOTA .NET decompiler, but much further along as a trustworthy inspection decompiler. The architecture is not the limiting factor. The limiting factors are high-level recovery coverage and SRM-native metadata infrastructure.

Preserve the SRM + NativeAOT boundary. Move Roslyn-dependent judging into harness/oracle code only. Build the missing metadata substrate aggressively. Then use the ledger plus adversarial fixtures to add recovery passes without giving up the project's strongest property: it knows when it does not know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment