Skip to content

Fix dynamic event-assignment decompilation leaking is-event opcode#3873

Open
siegfriedpammer wants to merge 1 commit into
masterfrom
fix-dynamic-event-assignment
Open

Fix dynamic event-assignment decompilation leaking is-event opcode#3873
siegfriedpammer wants to merge 1 commit into
masterfrom
fix-dynamic-event-assignment

Conversation

@siegfriedpammer

Copy link
Copy Markdown
Member

DynamicIsEventInstruction has no ExpressionBuilder visitor, so any is-event diamond that survives the transform pipeline leaks into the output as an "OpCode not supported: DynamicIsEventInstruction" comment.

DynamicIsEventAssignmentTransform only collapsed the plain statement form of d.Event += b. The three other lowerings the compiler emits were missed and leaked:

  • a copy-of-value temporary between the getmember cache and the diamond (modern Roslyn emits it for the statement form);
  • the result-used shape, where the diamond is a value-if nested inside the consuming expression (a call argument such as Console.WriteLine(d.Event += b), or a leave's value when returned);
  • the optimized result-returned shape, which splits into if (isevent) leave(add) plus a fall-through leave(compound) rather than an if/else.

The descendants walk now handles the nested value-if once an over-strict copy-of-value bounds guard (which never held for the compact 4-instruction return block) is dropped; the two-leaves shape gets a dedicated matcher. Re-running from the collapsed statement instead of the next one avoids indexing past the end of a block that has shrunk to a single leave.

New DynamicTests fixture cases cover the statement, result-used, and result-returned forms; the last is green across all compiler-matrix configs (optimized configs exercise the two-leaves matcher, debug configs the nested value-if path).

🤖 Generated with Claude Code

@siegfriedpammer siegfriedpammer force-pushed the fix-dynamic-event-assignment branch from d9e8e80 to ab1e54b Compare July 10, 2026 16:26
DynamicIsEventInstruction has no ExpressionBuilder visitor, so any is-event
diamond that survives the transform pipeline leaks as an "OpCode not supported"
comment. The collapse only fired for the plain statement form; every other
lowering the compiler emits for "d.Event += b" was missed:

- a copy-of-value temporary between the getmember cache and the diamond (modern
  Roslyn emits it for the statement form),
- the result-used shape, where the diamond is a value-if nested inside the
  consuming expression (a call argument, or a leave's value when returned),
- the result-returned shape as two leaves, "if (isevent) leave(add)" plus a
  fall-through "leave(compound)" rather than an if/else, and
- the same two-leaves shape when the optimizer drops the getmember cache (legacy
  csc and Roslyn <= 2.x): the is-event flag then feeds a single branch, inlining
  folds it into the condition, and the compound leave is already collapsed, so
  the cache-based entry never matched it and the branch leaked. These compilers
  only run on Windows CI, so the gap was invisible on Linux.

The descendants walk handles the nested value-if once an over-strict
copy-of-value bounds guard (which never held for the compact return block) is
dropped. The two two-leaves shapes share a skeleton matcher and the add/remove
accessor-name check, and both validate the accessor name and arguments. Re-running
from the collapsed statement instead of the next one avoids indexing past the end
of a block that has shrunk to a single leave.

Assisted-by: Claude:claude-opus-4-8:Claude Code
@siegfriedpammer siegfriedpammer force-pushed the fix-dynamic-event-assignment branch from ab1e54b to 0b241ad Compare July 11, 2026 05:04
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