Repository navigation
A global held across a call is given back when the call throws - #645
Merged
Merged
Conversation
holdGlobalArguments (#639, #643) retains a global read given to a call that may overwrite it and releases it after the call. A throw out of the call skipped the release: a try around the call caught the exception, and the global's block kept a count nobody gave back - under rc for any held value, under own and rc for a Shared<T> handle, where `Shared.count` showed it. When a try in the same function takes the call's exceptions, the held value now also goes into a slot made at the function's entry, null outside the call, and the handler that runs first (the try's cleanup, else its catch clause, else its finally; the finally for a call in a catch clause - the landing pad's own order) begins with a release of that slot. An exception that leaves the function still leaks the count, as it does for locals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 23, 2026
…global a count (#647) Two shapes the memory-model fuzzer (#646) found double-freeing under rc since #639. - holdGlobalArguments retained the argument right before the call. A later argument of the same call could give the global a new value first (`f(G, (G = mk(), x))`, `f(H.n, (H.n = mk(), x))`): its release freed the block, and the hold then retained and released a freed block. The retain (and the #645 unwind slot) now follow the read. - `const n = G` was folded into the read, holding no count. Once the global got a new value, here or in a call, `n` dangled (fuzzer R1). #639 made that a crash: `run(n.v)`, with `run` assigning G, held and released a field of the freed block. Under rc a const read out of a global, or an interface or bound method made over one, now gets storage like a const read out of a field or an element (#635, #641, #642, #644). Under own the folded read stays a borrow. 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.
This closes the gap left open in #643.
holdGlobalArgumentsretains a global read given to a call that may overwrite it, and releases it after the call. When the call threw, the release was skipped: a try around the call caught the exception, and the global's block kept a count nobody gave back.Shared<T>handle, whereShared.countshowed the extra count.Fix
This only applies when a try in the same function takes the call's exceptions (
unwindRegionOf):ReleaseSlotof that slot, then nulls it. That handler is chosen in the same order the landing pad uses (TryOpLowering):An exception that leaves the function with no try around the call still leaks the count, the same as owned locals do today.
Tests
own/own_shared_global_argument_throw.tsruns in theshared_modelsforeach (gc/none/rc/own) and in the rc/own--verify-ownershiplist. It covers try/catch, try/finally (caught outside), a block with a local in a try body (a cleanup-only try), a call in a catch clause with a finally, a try in a loop, and the normal path. On main it fails under rc and own.test-compile-rc-…andtest-compile-own-own_shared_global_argument_throw(and their jit twins). Without the switch they pass.ctest -C Release: 4958/4958. WSL Linux full run: 4940/4940.🤖 Generated with Claude Code