Skip to content

-mm=own: a local lent to a call cannot be assigned before the call takes it - #660

Merged
ASDAlexander77 merged 5 commits into
mainfrom
own-argument-slot-assigned
Oct 23, 2026
Merged

ASDAlexander77 merged 5 commits into
mainfrom
own-argument-slot-assigned

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Follow-up 8 from the own-raytrace rulings, own half (fuzzer A1, local form). Stacked on #657 (it uses that PR's own_facts::findBetween / slotOf): merge #657 first; until then this PR's diff shows both commits.

Bug

g(n, (n = mk(2), 0)): the first argument borrows n's object and the second gives n a new value first, which drops that object before g reads it. own accepted it and read freed memory (garbage, or exit 139 on Linux), also through a conditional argument, in a try body, and with a closure called there that assigns a captured n. On current main (with #655) own still accepted every local form; the global form (f2(G, (G = mk(2), G))) is already rejected by #639 / checkGlobalRead, and the field form by the place-borrow rule.

Fix

checkArgumentsAssigned (OwnershipInferencePass) rejects an argument read out of an owning local when a store into that local's slot, or its release - or a call, when a closure built over the local assigns it (OWN_ASSIGNS_CAPTURES_ATTR_NAME) - lies on a path from the read to the call:
'n' is lent to 'g', but is given a new value before the call takes it; -mm=own cannot prove it outlives the call, with a note at the assignment. A closure that only reads the captured local, or an assignment of another local, stays accepted.

Tests

  • own negatives: own_err_argument_assigned, ..._conditional, ..._closure.
  • Tooth: without the check own accepts all three and reads freed memory (printed garbage on Windows).

Verified (this branch, both commits)

  • Windows Release: 5281/5281.
  • Linux (WSL): 5263/5263; the new tests again under MALLOC_PERTURB_=165 MALLOC_CHECK_=3: 6/6.

🤖 Generated with Claude Code

Update

Merged #657's branch (now with main through #656). Windows 5315/5315; Linux 5297/5297, the new tests 20/20 under MALLOC_PERTURB_=165 MALLOC_CHECK_=3.

ASDAlexander77 and others added 3 commits October 23, 2026 09:34
…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>
…kes it

`g(n, (n = mk(2), 0))`: the first argument borrows `n`'s object, and
the second gives `n` a new value first, which drops that object before
`g` reads it. own accepted it and read freed memory - also through a
conditional argument (`g(n, c ? (n = mk(), 0) : 1)`), in a try body,
and with a closure called there that assigns a captured `n`. Follow-up
8, own half (fuzzer A1, local form); rc holds such an argument instead
(the parent commit).

checkArgumentsAssigned rejects an argument read out of an owning local
when a store into that local's slot, or its release - or a call, when a
closure built over the local assigns it - lies on a path from the read
to the call (own_facts::findBetween). A read of a global is already
checkGlobalRead's; a store into a field of the local, the place
borrows'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 force-pushed the own-argument-slot-assigned branch from 376b09c to 6e7f8ff Compare October 23, 2026 09:18
ASDAlexander77 and others added 2 commits October 23, 2026 10:21
#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>
…nt-slot-assigned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit e75d1e3 into main Oct 23, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the own-argument-slot-assigned branch October 23, 2026 09:56
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