Skip to content

rc: a new array given straight to a call is held across it - #649

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-rc-array-argument-held
Oct 23, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-rc-array-argument-held

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Fixes fuzzer bug R3 / R3b (#646): an alias of an array parameter, or the parameter reassigned, over-released a new array the caller had passed straight in.

function g(p: N[]) { for (let j = 0; j < 2; j++) { const a: N[] = p; } return p[0].v.x; }
g([mk(6)]);                              // 0xC0000374 / SIGSEGV
function h(p: N[]) { try { let a = p; } finally { p.push(mk(3)); } }
function k(p: N[]) { p = [mk(6)]; ... }  // the assigned parameter is copied into an owned local

Why

Under rc a new array is created at count 0 (bornUncounted). One built for an argument, such as g([mk(6)]), had no other owner. Any count the callee took and gave back dropped it to 0 and freed an array the callee was still using:

  • an alias of the parameter in a loop or a try (const a = p);
  • the parameter assigned, which copies it into an owned local first.

Nothing ever gave such an array back after the call either, so it leaked.

Fix (rc only)

holdGlobalArguments now also holds an array made for the call: a new array (CreateArray, NewArray, NewEmptyArray), or a constant array copied to the heap by its cast, whose only use is the call (through casts). It is retained where it is made and released after the call, using the same mechanism as the global hold (#647), including the #645 unwind slot. That release also gives the array back.

Tests

tests/00array_argument_held.ts covers:

  • an alias in a loop
  • an alias in a try with a push in the finally
  • the parameter assigned
  • an array passed on to a call that assigns its parameter
  • a constant array

It runs compiled, under JIT, and in the rc/none corpus, and it crashes under rc on main on Linux.

Verification

  • ctest -C Release: Windows 4982/4982, WSL Linux 4964/4964.
  • Replaying the fuzzer's repros on Linux: R3-array-param-loop, R3-array-param-try and R3b-array-param-reassigned now match gc. Disagreements went from 22 to 19.
  • mm_fuzz.py --jobs 10 run --count 500 --seed 1: only the 3 known own failures (R1, O4).

🤖 Generated with Claude Code

A new array is born at count 0 under rc, and one built for an argument
(`g([mk(1)])`) had no other owner. A count the callee took and gave back
freed the array the callee still read: an alias in a loop or a try
(`const a = p`), or the parameter assigned, which copies it into a local
that owns it. Nothing ever gave such an array back either.

holdGlobalArguments now also holds an array made for the call (a new
array, or a constant array copied to the heap by its cast, used only by
the call): retained where it is made, released after the call. Fuzzer
(#646) R3 / R3b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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