Repository navigation
-mm=own gaps: fuzz-found borrowing literals, let-held getter reads; rc conditional record literal - #758
Merged
Conversation
…ranch made
`c ? { start: this.pos, dir: new V(1) } : { ... }`: each branch yields the literal it built, whose
field holds an object the branch made, and the branch gave that object back before it ended. The
receiver's retain after the `ts.If` - which retains the literal's fields - came too late, and the
field was freed under it (heap corruption, 0xC0000374; hand-written fuzz seed review7__pM1).
consumeConditionalResults now treats such a branch as it treats one yielding a field of a temporary
it made: the branch retains what it yields. Not under own, which settles a literal's fields itself
(and rejects this shape).
Test: tests/00conditional_record_literal_temporary.ts (gc, and rc/none through the corpus).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ds 73095, 73544, 74808)
A 2000-program mm_fuzz round (seeds 73000-74999) found three own crashes among 1735 accepted programs;
each is now a compile error.
- An array literal argument that borrows its elements (`f([HG.v])`, classifyArgumentArrays) given to a
callee that may overwrite an element (`p[0] = v`) or destroy anything: the overwrite freed the
global's value and the new element was lost with the shallow release. Such an array no longer
borrows, so the element read is the error it is anywhere else.
- A borrowing record literal (`{ v: n.items[0] }`) given to a callee whose known overwrites include a
field (`p.v = ...`): walkRecord treats it as a write of the field here, which keeps the record.
- walkRecord treated a `ts.Invoke` (a call inside `try`) as a plain branch and skipped it, so a
borrowing record given to a call in a `try` had no use there: `f({ v: n.items[0] }, (n = mk(), n))`
freed the element before the call read it.
Tests: own_err_literal_element_overwritten, own_err_record_field_overwritten,
own_err_record_borrow_in_try (each compiled without error before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… at the returns `let x = i.item; for (...) use(x); return x.n` - a read through an interface field a getter may fill, held in a `let` read across blocks - was never freed: #751 releases such a read only when it and what is borrowed from it stay in its block. Two million calls kept two million Boxes (280.5 MB peak; 4.3 MB now). releaseCellReads now also frees a read that is the initializer of a `let` nobody assigns or captures, whose reads stay in the function (staysInBlock over any block), when the read reaches every return and the object it reads through is a parameter's, which outlives them: the same conditional release, at each return. A read returned or stored still leaks on the getter's path. Tests: own_interface_getter_let (own, no counting, rc, gc, none) and test-own-interface-getter-let-released, which reads the IR (fails before the fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ock is an error User ruling (2026-10-31): `return i.item`, where a class of the module fills `item` with a getter, is a compile error rather than a leak. On the getter's path the read is a new value only its own release frees; returned (a borrow of `i` to the caller), stored, or kept where neither the in-block release nor the `let` release reaches, it was lost. releaseCellReads now reports such a read when a class in the module fills a field of that name with accessors (its `<class>.<interface>..vtbl.<field>.accessor` cell); a field no class fills that way is a borrow on every path and may still be returned. A method called on the read (`i.item.get()`) is followed by staysInBlock now (the bound method, its object and function, the call), so it is freed rather than rejected. DefaultLib src/lib.ts compiles under own and rc with no errors. Test: own_err_getter_read_returned. Release ctest 6506/6506; mm_fuzz seeds 76000-76399 clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`bag.forEach(b => { last = new Box(b.n) })` was rejected: the assignment through a captured
variable's cell in the closure body was a drop of anything (fieldDropsInBody), so a method that walks
the array a field holds and calls the callback had its element borrow "used after it may be released".
Spec 52 made this precise for numbers and strings; this does it for objects and arrays.
- The assignment is a place drop of its own, OWN_CAPTURED_CELL_POSITION: it frees what only the cell
owns, so it drops a chain read through a captured variable's cell (dropsChain), nothing else.
- A call given anything read through a variable a closure assigns, when the call may run such a
closure, is an error where it is made (checkArgumentsAssigned): no parameter of the callee can be
under the value the closure frees. `bag.forEach(b => { bag = new Bag() })` is that error.
Tests: own_callback_assigns_capture (own, no counting, rc, gc, none; rejected before),
own_err_callback_assigns_receiver, own_err_callback_assigns_borrowed. DefaultLib src/lib.ts compiles
under own and rc with no errors.
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.
A batch of
-mm=owngap fixes (plus one rc fix), from a new fuzz campaign and the open leak list. There are five commits, each with its own tests.1. rc: a conditional between record literals keeps the objects its branch made (
d82cfbea)c ? { start: this.pos, dir: new V(1) } : {...}caused heap corruption under rc. The hand-written fuzz seedreview7__pM1still failed on main. Each branch gave back itsnew Vbefore the receiver after thets.Ifretained the literal's fields.consumeConditionalResultsnow makes such a branch retain the literal it yields, as it already does for a field read out of a branch temporary.tests/00conditional_record_literal_temporary.ts(gc, plus rc and none through the corpus).2. own: three borrowing literals that compiled and then crashed (
edeb47a9)A 2000-program
mm_fuzzround (seeds 73000-74999) found 3 own crashes among the 1735 programs own accepted. Each is now a compile error:f([HG.v])wherefdoesp[0] = v: an array literal that borrows its elements no longer borrows when its callee may overwrite an element or destroy anything.f({ v: n.items[0] })wherefdoesp.v = ...: a borrowing record given to a callee whose overwrites include a field is treated as a write of that field.walkRecordtreatedts.Invoke(a call insidetry) as a branch and skipped it. A borrowing record given to a call in atrywas therefore never checked:f({ v: n.items[0] }, (n = mk(), n))freed the element first.own_err_literal_element_overwritten,own_err_record_field_overwritten,own_err_record_borrow_in_try. Each compiled without error before this change.3. own: a getter-filled interface field read put in a
letis freed at the returns (e5f304ba)let x = i.item;read across a loop leaked on the getter's path, because #751 only frees reads that stay in their block. For 2M calls, peak working set went from 280.5 MB to 4.3 MB. The read is released at each return when all of these hold:letis only read, and nothing read from it leaves the function;A read that would still leave its block is now an error (commit 4).
own_interface_getter_let(own, no-counting, rc, gc, none) andtest-own-interface-getter-let-released, which reads the IR (fails before the fix).4. own: a getter-filled interface field read that would leave its block is an error (
a588196d, your ruling)return i.itemis now a compile error when a class in the module fillsitemwith a getter. Before, it leaked one value per call on the getter's path. A field that no class fills with accessors is a borrow on every path and may still be returned. A method called on the read (i.item.get()) is now followed, so that read is freed rather than rejected. The DefaultLib'ssrc/lib.tscompiles under own and rc with no errors.own_err_getter_read_returned.5. own: a callback may give a captured object a new value (
9804fff0, item 4)bag.forEach(b => { last = new Box(b.n) })was rejected. Assigning a captured variable in a closure counted as "may destroy anything", so a method that walks a field's array and calls the callback could not borrow its elements. Spec 52 already handled numbers and strings precisely; this extends it to objects and arrays:OWN_CAPTURED_CELL_POSITION. It drops only borrows read through a captured variable's cell.bag.forEach(b => { bag = new Bag() })).own_callback_assigns_capture(own, no-counting, rc, gc, none; rejected before),own_err_callback_assigns_receiver,own_err_callback_assigns_borrowed.The same commit also fixes an older own crash found by fuzz seed 77055, which predates this batch:
f(n, (n = mk(3), ...)), wherenis given as an interface and reassigned in a later argument.checkArgumentsAssignedlooked through casts but not through the interface view, so it never foundn. It now looks through views.own_err_interface_arg_assigned_in_call.Verification
review6r1__v1/v1b. Those are rc crashes from a function cast throughas anyto another interface's signature; they are not addressed here.🤖 Generated with Claude Code