Skip to content

rc: hold an argument whose place the call overwrites - #653

Merged
ASDAlexander77 merged 5 commits into
mainfrom
fix-rc-argument-place-overwritten
Oct 23, 2026
Merged

ASDAlexander77 merged 5 commits into
mainfrom
fix-rc-argument-place-overwritten

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Fixes fuzzer bugs A1 (local, parameter and local-field forms) and R8 (#646) under rc.

s += f(n, (n = mk(3), n), ..);           // A1: n is freed before f runs - SIGSEGV
s += f(h.n, (h.n = mk(4), h.n), ..);     // A1, a local's field
f1(n8, n8.items[0]);                     // R8: f1 does `p2.items[0] = mv(4)`, then reads p3

Why

Arguments are borrowed. In both shapes the place an argument was read from is overwritten while the call still uses it:

Fix (rc only)

holdGlobalArguments now also holds:

  • an argument read from a slot or a place that something between the read and the call overwrites (a ts.Store or ts.ReleaseSlot to the same place; a new helper samePlace compares one variable, one global, or one field of one value);
  • an argument read from a field or an element of a value passed as another argument of the same call.

The overwrite check walks out through the fields and elements the argument was read through (follow-up commit 10b6e91). So f({ v: n.v }, (n = mk(), 0)) and f(n.items[0], (n = mk(), 0)) are held as well: there the later argument gives back the object the argument was read out of. The fuzzer found these past its first 1000 programs, and localsFieldInLiteral and localsElement in the test cover them.

As with the global hold (#647/#648), it is retained where it is read and released after the call.

Tests

tests/00argument_place_overwritten.ts covers:

  • a local given before an argument that assigns it, once and in a loop
  • a parameter given the same way
  • a class field given the same way
  • an element, and a field, passed with their object and overwritten through it

On Linux main, the local, field and R8 cases each fail on their own. The parameter case already passes on main and is kept as coverage. The test runs compiled, under JIT, and in the rc/none corpus.

Verification

🤖 Generated with Claude Code

ASDAlexander77 and others added 5 commits October 23, 2026 05:25
An argument is borrowed, and two shapes overwrote the place it was read
from while the call still used it:

- a later argument of the same call assigns it: `f(n, (n = mk(), n))`
  for a local, a parameter or a local's field (A1). #647 covered a
  global; a local was still freed before the call ran.
- the callee is given the object as well: `f(n, n.items[0])`, where `f`
  overwrites `p.items[0]` and then reads its other parameter (R8).

Under rc holdGlobalArguments now also holds an argument read out of a
slot or a place that something between the read and the call
overwrites, and an argument read out of a field or an element of a value
given as another argument of the call: retained where it is read,
released after the call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`f({ v: n.v }, (n = mk(), 0))` and `f(n.items[0], (n = mk(), 0))`: the
argument is read out of a field or an element of `n`, and the later
argument's assignment to `n` gives back the object it was read out of.
overwrittenBeforeCall looked only at the argument's own place; it now
walks out through the fields and elements it was read through, and
holds the argument when any of those values' places is overwritten
before the call. Found by the fuzzer (#646) past the first 1000
programs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-overwritten

# Conflicts:
#	tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp
#	tslang/test/tester/CMakeLists.txt
@ASDAlexander77
ASDAlexander77 merged commit 364a56a into main Oct 23, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-rc-argument-place-overwritten branch October 23, 2026 09:16
ASDAlexander77 added a commit that referenced this pull request Oct 23, 2026
#653 added a same-block check for an argument whose slot or place a later argument overwrites (overwrittenBeforeCall); it runs before localAssignedBefore, which still covers a conditional argument, a closure assigning a captured local, a field of a folded const, and a handle under own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77 added a commit that referenced this pull request Oct 23, 2026
…nt-slot-assigned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77 added a commit that referenced this pull request Oct 23, 2026
…other name (#663)

`f7((p12.n = mk(7), p12.n), (HG.n = mk(9), p13))` with `p12` being
`HG`: the first argument is read out of `p12.n`, and the second
argument's `HG.n = mk(9)` gives back the block it read. The caller
holds an argument whose place is overwritten between the read and the
call (#653), but samePlace compared places by name. A store to one
field of another object of the same class, or to an element of another
array of the same type, may be the same place, and now holds the
argument as well. Fuzzer (#646), 80037.

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