Repository navigation
rc: a local read for an argument is held when a later argument assigns it - #657
Merged
Merged
Conversation
…s it `g(n, (n = mk(2), 0))`: the arguments are read in order, so the first read `n` and the second gave `n` a new value before the call took the first - and under rc that assignment released the block the callee then read (exit 139, or garbage). The same with a field or an element of a local (`g(h.v, (h.v = mk(), 0))`, also of a folded `const h = new H()`), a conditional argument (its own blocks), a call in a try body, an assigned parameter, and a closure called there that assigns a captured local. Follow-ups 7 and the rc half of 8 (fuzzer A1, local form). holdGlobalArguments now also holds such an argument, as it holds one read out of a global (#647): retained right after the read, released after the call (and in the handler of a try that takes what the call throws). It holds a read of a local when a store into that local, a place in it, or its release - or any call, for a captured local - lies on a path from the read to the call (own_facts::findBetween). The retain goes right after the read also when a conditional argument put the call in a later block of the same region, unless a loop leads back. own still accepts the local forms; that is the own half of follow-up 8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…signs it `g(s, (s = new Shared(new V(2)), 0))` with `s` a local handle: the assignment gave back the count the old handle held, the last one, and `g` read the freed block. Handles are counted under own too, and the previous commit held such a read under rc only, while own's new check leaves handles to the counts - so own read freed memory. The hold now applies to a handle under own as well, as holdGlobalArguments already does for a handle read out of a global. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up 7 from the own-raytrace rulings, and the rc half of 8 (fuzzer A1, local form).
Bug
g(n, (n = mk(2), 0)): arguments are read in order, so the second gavena new value before the call took the first, and under rc that assignment released the block the callee then read (exit 139 or garbage; probes review8r3 L5, L6, L6b, L7, L7h, h.ts, fuzzer A1-local). Also:g(h.v, (h.v = mk(), 0)), also of a foldedconst h = new H());g(n, c ? (n = mk(), 0) : 1)) - the call lands in a later block;n.Fix
holdGlobalArgumentsnow also holds a read of a local, as #647 holds a read of a global: retained right after the read, released after the call (and in the handler of a try that takes what the call throws). It holds when a store into the local, a place in it, or its release - or any call, for a captured local - lies on a path from the read to the call: newown_facts::slotOf/slotWritten/findBetween(OwnershipFacts.h), which follow the blocks a conditional argument makes. The retain also goes right after the read when the call is in a later block of the same region and no loop leads back (before, only same-block reads; this also helps a global read in a conditional argument list).own still accepts the local forms here; its half is the next PR (stacked on this one).
Tests
tests/00argument_slot_assigned.ts(gc, rc, none; AOT + JIT): each form above, with allocations of the same size before each read, plus a call that throws.Verified
MALLOC_PERTURB_=165 MALLOC_CHECK_=3: 6/6.🤖 Generated with Claude Code
Update
Shared<T>local reassigned in a later argument (g(s, (s = new Shared(..), 0))) is held under own too (handles are counted there; own read freed memory). Testown/own_shared_argument_assigned.tsin the shared-handle loops (gc, none, rc, own + verify-ownership).overwrittenBeforeCall(same-block slot/place overwrite). Both are kept: rc: hold an argument whose place the call overwrites #653's runs first;localAssignedBeforestill covers a conditional argument, a closure assigning a captured local, a field of a folded const, and a handle under own.MALLOC_PERTURB_=165 MALLOC_CHECK_=3.mm_fuzz.py run --count 500 --seed 1on Linux (this + -mm=own: a local lent to a call cannot be assigned before the call takes it #660): 924 programs, 0 rc failures, 1 own failure = fuzzer bug O4 (aSharedparameter reassigned in try/finally), pre-existing and unrelated.