Skip to content

Fix #3891: don't reduce nesting when there is no else block - #3898

Merged
siegfriedpammer merged 1 commit into
masterfrom
fix-3891
Jul 20, 2026
Merged

Fix #3891: don't reduce nesting when there is no else block#3898
siegfriedpammer merged 1 commit into
masterfrom
fix-3891

Conversation

@siegfriedpammer

Copy link
Copy Markdown
Member

Fixes #3891System.InvalidCastException: Unable to cast object of type 'ICSharpCode.Decompiler.IL.Nop' to type 'ICSharpCode.Decompiler.IL.Block' thrown from ReduceNestingTransform.ExtractElseBlock while decompiling a method.

Root cause

ReduceNesting walks an else-if chain to its innermost if and, if the nesting heuristic approves, extracts the else block via ExtractElseBlock, which does (Block)ifInst.FalseInst. When the chain has no trailing else, that FalseInst is a bare Nop, so the cast throws.

The real defect is upstream of the cast: ShouldReduceNesting was handed that Nop and returned trueUpdateStats(Nop) counts it as one statement, and with maxStatements == 0 the maxStatements2 >= 2 * maxStatements term becomes 1 >= 0. So the heuristic gave a nonsensical "yes, reduce" for something that isn't a reducible block, and the cast is just where that detonated.

Fix

ShouldReduceNesting now takes a Block instead of an ILInstruction, so the precondition is enforced by the type rather than discovered at runtime, and the "is there an else block" check moves to the call site in ReduceNesting (a chain with no trailing else simply isn't reduced). The switch caller already passed a real Block, so it is unchanged.

Verification

  • The exact ILAst that previously threw now completes cleanly.
  • No behaviour change for any real block: the guard is inert whenever FalseInst is a Block (every genuine else-if reduction and the switch/default path), so working output is byte-identical. Confirmed by decompiling System.Private.CoreLib and System.Linq.Expressions with and without the change — identical error counts.

Note on tests: the trigger requires an else-if chain with no final else whose branches score zero statements, which no C# compiler emits from ordinary source (it collapses empty branches, turns unreachable ones into sibling ifs, etc.) — the reported assembly is IL-woven. A compiled ILPretty fixture therefore isn't reachable without the original IL, so no test is included here.

🤖 Generated with Claude Code

ReduceNesting walks an else-if chain to its innermost if and asks
ShouldReduceNesting whether to extract the else block, which ExtractElseBlock
does by casting the block to Block. A chain with no trailing else reaches this
with a bare Nop, yet the heuristic still approved it (its stats count a Nop as
one statement), so the cast threw InvalidCastException. Take a Block in
ShouldReduceNesting and skip the reduction at the call site when the else is
absent.

Assisted-by: Claude:claude-opus-4-8:Claude Code
@siegfriedpammer
siegfriedpammer merged commit 6ba7d59 into master Jul 20, 2026
13 checks passed
@siegfriedpammer
siegfriedpammer deleted the fix-3891 branch July 21, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.InvalidCastException: Unable to cast object of type 'ICSharpCode.Decompiler.IL.Nop' to type 'ICSharpCode.Decompiler.IL.Block'.

1 participant