Skip to content

Qualify shadowed instance field loads with this.#607

Merged
richlander merged 1 commit into
mainfrom
fix/decompiler-shadowed-field
Jun 18, 2026
Merged

Qualify shadowed instance field loads with this.#607
richlander merged 1 commit into
mainfrom
fix/decompiler-shadowed-field

Conversation

@richlander

Copy link
Copy Markdown
Owner

Problem

The decompiler printed an instance field load as the bare field name. When a
parameter or local shadows the field, the bare name binds to the local instead
of the field. The compile-back oracle caught this on the Shadowed fixture:

int _shadowed = 1;
public int Shadowed(int _shadowed) => this._shadowed + _shadowed;

decompiled to return _shadowed + _shadowed; — reading the parameter twice and
dropping the this._shadowed field load entirely. Invisible to --compile-check
and --grade-source because the output still parses and binds; only the IL
differs.

Fix

In CSharpPrinter.FieldTarget, render this.{field} for a this-receiver
instance field load when the field name collides with a parameter or local in
scope; unshadowed fields stay bare per the taste convention. This mirrors the
existing auto-property backing-field branch, which already qualifies with this.
for the same shadowing reason.

Verification

  • --compile-back on CfgSampleClass: opcode-exact goes from 82 to 83; the
    Shadowed docket entry is resolved.
  • New regression test IrImporterTests.Shadowed_QualifiesFieldLoadWhenParameterShadows.
  • Full ILInspector.Decompiler.Tests suite green (425 passing).

Part of the compile-back oracle docket (#604). Related: #605.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

The decompiler printed an instance field load as the bare field name, so
when a parameter or local shadowed the field (e.g. int Foo(int _x) =>
this._x + _x) the recompiled name bound to the local, not the field. The
compile-back oracle flagged Shadowed reading the parameter twice instead
of the field.

Render this.{field} when the field name collides with a parameter or
local in scope, matching the existing auto-property backing-field branch;
unshadowed fields stay bare per the taste convention. CfgSampleClass goes
from 82 to 83 opcode-exact under --compile-back.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@richlander richlander merged commit 3f7dcf8 into main Jun 18, 2026
19 of 20 checks passed
@richlander richlander deleted the fix/decompiler-shadowed-field branch June 18, 2026 06:26
richlander added a commit that referenced this pull request Jun 18, 2026
The compile-back oracle (decompile -> recompile -> compare IL) only ran as
a console exploration mode in the harness. Wire it into CI as a durable
regression guard.

Expose CompileBack.Evaluate: a non-printing, structured-result entry point
that shares all of the skeleton-emission and opcode-comparison machinery
with the existing console Run, so the two paths cannot drift. The test
project links the single CompileBack.cs source file (rather than taking a
project reference on the harness Exe, which would pull in a second entry
point) and adds the Roslyn package it needs.

CompileBackGateTests asserts two properties over CfgSampleClass:
- no method newly recompiles to a different opcode stream beyond the
  documented KnownDiffs docket (catches a fix in one method silently
  degrading another);
- previously-fixed methods (CheckedAdd #604, UnsignedShift #606, Shadowed
  #607) stay opcode-exact.

Shrink KnownDiffs as docket entries are fixed; StaleFieldRead remains a
tracked defect (issue #605).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant