Skip to content

-mm=rc: a parameter the body assigns owns what it is assigned - #515

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-rc-param-instanceof-loop
Oct 5, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-rc-param-instanceof-loop

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Closes #512.

Under -mm=rc, a parameter's slot is borrowed from the caller and takes no reference, so assigning to it neither retained nor released. x = new B(1) stored an instance that the block it was made in then released, and x pointed at freed memory from there on.

  • In the issue's while (x instanceof A) { x = new B(1) }, the next condition read it and crashed.
  • More generally, any parameter assigned inside a block and read after it got whatever had been allocated over it since. On main under rc, an optional parameter, a method's parameter and a string built in a loop all come back as garbage.
  • gc and none are unaffected; -mm=own refuses parameter assignment, unchanged.

Change

mlirGenFunctionOwnAssignedParams (MLIRGenFunctions.cpp) runs before the body is generated, under rc only.

  1. It scans the body for assignments to each parameter that holds a heap reference:
    • plain and compound assignments (=, +=, ??=, ...)
    • destructuring targets
    • for (x of ...) / for (x in ...)
  2. Each such parameter is copied into a local of the same name. A local owns what it holds (takeOwnershipOfLocal): a reference is taken on the caller's value, each assignment hands the count over, and the function's exit gives it back. The copy is listed with the function's own scope.

The scan over-approximates on purpose: it walks nested functions and counts every name in a destructuring target. A copy that wasn't needed costs one retain/release pair; a missed one is this bug.

Test

00param_assigned_owned.ts reads the parameter after reusing the heap in every case:

  • the two instanceof loops from the issue (while and for)
  • assignment in an if, in a loop, in a block whose variable a closure captured, and inside a closure
  • optional, defaulted and ??= parameters
  • returning the assigned parameter
  • destructuring and for...of into it
  • a method, an arrow function, a generator, an async function and a rest parameter
  • a string, and an unassigned parameter

On main the first assertion crashes under rc. With the change it passes under gc, rc and none, with and without --opt, compile and JIT. --verify-ownership is clean.

00for_condition_narrowing.ts (#514) used a local copy because of this bug; its instanceof case reassigns the parameter again, so it now guards #512 too.

Verification

  • ctest -C Release: 3882/3882 passed, including the 8 ownership-verifier shards. The generator / async / rest cases were added to the test afterwards; that test's 6 registrations and the verifier shards (14 tests) were rerun and pass. gtest unittests are not built in this tree.
  • DefaultLib (origin/main) built for rc with this compiler: release compile and JIT 159/160 passed, with 1 skip (a gc-only test).

🤖 Generated with Claude Code

A parameter's slot is borrowed from the caller and takes no reference,
so assigning to it neither retained nor released. `x = new B(1)` stored
an instance the block that made it then released, and `x` pointed at
freed memory from there on. In `while (x instanceof A) { x = new B(1) }`
the next condition read it and crashed (#512). After an `if` or a loop,
the parameter read whatever had since been allocated over it: an
optional parameter, a method's, a string built in a loop all came back
as garbage. Only -mm=rc: a collector needs none of this, and -mm=own
refuses the assignment.

Before the body is generated, mlirGenFunctionOwnAssignedParams scans it
for assignments to each parameter that holds a heap reference: plain
and compound assignments, destructuring targets, and `for (x of ...)` /
`for (x in ...)`. Nested functions are included, since naming one too
many costs only a retain and a release. Each one found is copied into a
local of the same name. A local owns what it holds
(takeOwnershipOfLocal): a reference is taken on the caller's value, each
assignment hands the count over, and the function's exit gives it back.
The copy is listed with the function's own scope, after the parameter
cells.

00param_assigned_owned.ts: the instanceof loops from #512 (while and
for), assignment in an if, in a loop, in a block a closure captured
from, and inside a closure; optional, defaulted and `??=` parameters;
returning the assigned parameter; destructuring and for...of into it; a
method, an arrow function, a generator, an async function, a rest
parameter, a string, and an unassigned parameter. Each
reads the parameter after the heap has been reused. On main the first
assertion crashes under -mm=rc. 00for_condition_narrowing.ts loops on
the parameter again instead of a local copy.

Closes #512

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 240c5ee into main Oct 5, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-rc-param-instanceof-loop branch October 5, 2026 17:10
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.

-mm=rc: reassigning a parameter in a loop conditioned on its instanceof crashes

1 participant