Skip to content

Fix #4059: treat a Deconstruct out argument as a definition - #4114

Draft
siegfriedpammer wants to merge 1 commit into
masterfrom
fix/4059-deconstruct-out-slots
Draft

Fix #4059: treat a Deconstruct out argument as a definition#4114
siegfriedpammer wants to merge 1 commit into
masterfrom
fix/4059-deconstruct-out-slots

Conversation

@siegfriedpammer

Copy link
Copy Markdown
Member

Fixes #4059.

Two deconstructions of the same source type in one method are not recognized: the first one alone
decompiles correctly, but as soon as a second follows, both fall back to raw Deconstruct calls,
because csc reuses the same two out-slot temporaries and hands them to the second call in the
opposite order.

Why splitting the temporaries did not happen

ReachingDefinitionsVisitor does not track address loads, so a variable that is only ever written
through its address - which is every out slot - stays potentially uninitialized for its whole
life. SplitVariables.GroupStores merges all such loads of one variable into a single
representative, so the two deconstructions end up in one live range and neither can be matched.

The same holds for out arguments in general; it only becomes visible for Deconstruct, where
pattern matching depends on the split.

The fix

An ldloca in argument position > 0 of a Deconstruct call is treated as a definition: it kills
the definitions reaching it and becomes the one that reaches the following loads. That reuses the
existing store machinery, so it stays flow-sensitive - where two such calls join, both reach the
load and are merged again, which a flow-insensitive rule would get wrong.

IL cannot express an address that is assigned without being read first; out is a C# convention
over ref. So the qualifying address loads have to be named one by one, and a Deconstruct
method written in IL could read its argument before assigning it. That possibility is knowingly
ignored - C#'s definite assignment rules rule it out for anything a compiler emits.

SplitVariables itself is unchanged: its HandleLoad on such an ldloca now runs against the
post-store state and merges the instruction with itself.

Tests

The TwoBackToBackDeconstructs_Custom case that #4059 was filed from is checked in commented out;
this enables it. It fails on all eight compiler configurations before the change and passes after.

ICSharpCode.Decompiler.Tests: 3604 tests, no failures, 45 skipped (Windows-only configurations).

Prepared by an AI agent (Claude, claude-opus-5, via Claude Code) and reviewed by @siegfriedpammer.

The reaching-definitions analysis does not track address loads, so a
variable that is only ever written through its address stays potentially
uninitialized for its whole life. SplitVariables merges every such load
into one live range, which is why csc lowering two deconstructions onto
the same pair of temporaries left neither of them recognizable.

IL cannot express that a method assigns through an address without
reading it first - 'out' is a C# convention over 'ref' - so the address
loads that qualify have to be named. A Deconstruct method is the one
case that matters here, and C#'s definite assignment rules rule out a
read; a Deconstruct method written in IL could read the argument, and
that possibility is knowingly ignored.

Assisted-by: Claude:claude-opus-5:Claude Code
@dgrunwald

Copy link
Copy Markdown
Member

This needs proper investigation of how it interacts with the derived class (SplitVariables.GroupStores) -- and especially the partial support for ref locals that has.

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.

Two back-to-back deconstructions in one method are not recognized

2 participants