Skip to content

[mono][interp] Fix miscompile of self-assignment via newobj with alised byref args. - #131586

Merged
lateralusX merged 2 commits into
dotnet:mainfrom
lateralusX:lateralusX/fix-122237
Jul 31, 2026
Merged

[mono][interp] Fix miscompile of self-assignment via newobj with alised byref args.#131586
lateralusX merged 2 commits into
dotnet:mainfrom
lateralusX:lateralusX/fix-122237

Conversation

@lateralusX

Copy link
Copy Markdown
Member

The interpreter's optimized tier (INTERP_OPT_SUPER_INSTRUCTIONS) miscompiled a self-reassignment through a constructor whose in (byref) parameters alias the destination, e.g.:

a = new GEJ(a.x, a.y, a.z, a.infinity);

Per ECMA-335, newobj must construct into a temporary and only then copy the result to a, so the constructor observes the old value of a through the in pointers. The interp_super_instructions "forward dreg" pass was retargeting the constructed value's store directly into the address-taken local a (def->dreg = dreg), eliminating the intermediate move. This made the constructor read the very storage it was simultaneously writing, zeroing the fields once the method tiered up to the optimized tier (observed on Android after ~1000 iterations, thats when the interpreter tiering kicks in).

Add an address-taken guard (var_has_indirects) before the retarget, bailing out when either the source or destination local has had its address taken. This mirrors the existing guard in interp_cprop and the Mono JIT vreg_is_volatile discipline in local-propagation.c.

Add a regression test under JIT/Regression/JitBlue/Runtime_122237. It fails before the fix and passes after, verified under both the default (auto) and forced interpreter tiering modes.

Fixes #122237

…sed byref args

The interpreter's optimized tier (INTERP_OPT_SUPER_INSTRUCTIONS) miscompiled
a self-reassignment through a constructor whose `in` (byref) parameters
alias the destination, e.g.:

    a = new GEJ(a.x, a.y, a.z, a.infinity);

Per ECMA-335, `newobj` must construct into a temporary and only then copy the
result to `a`, so the constructor observes the old value of `a` through the
`in` pointers. The `interp_super_instructions` "forward dreg" pass was
retargeting the constructed value's store directly into the address-taken
local `a` (`def->dreg = dreg`), eliminating the intermediate move. This made
the constructor read the very storage it was simultaneously writing, zeroing
the fields once the method tiered up to the optimized tier (observed on Android
after ~1000 iterations, thats when the interpreter tiering kicks in).

Add an address-taken guard (`var_has_indirects`) before the retarget, bailing
out when either the source or destination local has had its address taken. This
mirrors the existing guard in `interp_cprop` and the Mono JIT `vreg_is_volatile`
discipline in local-propagation.c.

Add a regression test under JIT/Regression/JitBlue/Runtime_122237. It fails
before the fix and passes after, verified under both the default (auto) and
forced interpreter tiering modes.

Fixes dotnet#122237
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @BrzVlad, @kotlarmilos
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a Mono interpreter optimized-tier miscompile by preventing a copy-elimination/forwarding optimization from retargeting stores involving address-taken locals, and adds a regression test to cover the self-assignment + newobj + aliased in-byref scenario.

Changes:

  • Add an indirect-local (address-taken) guard in interp_super_instructions before forwarding a definition’s dreg across a mov.
  • Add a new JIT regression test reproducing the self-reassignment-through-aliased-in-args pattern.
  • Wire the new regression test into the existing Regression_ro_2 test project.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/mono/mono/mini/interp/transform-opt.c Skips the “forward dreg” retargeting optimization when either source or destination local is indirect/address-taken.
src/tests/JIT/Regression/Regression_ro_2.csproj Adds the new Runtime_122237 test file to the compile items.
src/tests/JIT/Regression/JitBlue/Runtime_122237/Runtime_122237.cs New regression test validating that repeated a = new GEJ(a.x, a.y, a.z, a.infinity) doesn’t corrupt a after tiering/optimization.

Comment thread src/mono/mono/mini/interp/transform-opt.c Outdated
Copilot AI review requested due to automatic review settings July 30, 2026 15:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/mono/mono/mini/interp/transform-opt.c:3855

  • The new indirect-local guard uses an empty if (...) { /*comment*/ } else if (...) pattern. It works, but the empty block makes the control flow easy to misread and is inconsistent with nearby patterns (e.g., interp_cprop uses a single combined condition). Consider folding !var_has_indirects into the main condition and dropping the empty block.
						int dreg = ins->dreg;
						if (var_has_indirects (td, dreg)) {
							// Don't bother with indirect locals
						}
						// if var is not ssa or it is a renamed fixed, then we can't replace the dreg

@z42-lang

z42-lang commented Aug 1, 2026

Copy link
Copy Markdown

can you backport to release/10.0 branch?

@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Aug 2, 2026
@lateralusX

Copy link
Copy Markdown
Member Author

/backport to release/10.0

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Started backporting to release/10.0 (link to workflow run)

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@lateralusX backporting to release/10.0 failed, the patch most likely resulted in conflicts. Please backport manually!

git am output
$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch

Applying: [mono][interp] Fix miscompile of self-assignment via newobj with aliased byref args
Using index info to reconstruct a base tree...
A	src/tests/JIT/Regression/Regression_ro_2.csproj
Falling back to patching base and 3-way merge...
CONFLICT (modify/delete): src/tests/JIT/Regression/Regression_ro_2.csproj deleted in HEAD and modified in [mono][interp] Fix miscompile of self-assignment via newobj with aliased byref args.  Version [mono][interp] Fix miscompile of self-assignment via newobj with aliased byref args of src/tests/JIT/Regression/Regression_ro_2.csproj left in tree.
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 [mono][interp] Fix miscompile of self-assignment via newobj with aliased byref args
Error: The process '/usr/bin/git' failed with exit code 128

Link to workflow output

@z42-lang

z42-lang commented Aug 4, 2026

Copy link
Copy Markdown

@lateralusX backporting to release/10.0 failed, the patch most likely resulted in conflicts. Please backport manually!

git am output

@lateralusX

@lateralusX

Copy link
Copy Markdown
Member Author

Will do manual backport during the week.

@lateralusX

Copy link
Copy Markdown
Member Author

#131927

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Passing in parameters in constructor of read-only struct shouldn't be allowed

4 participants