Skip to content

A const object literal holding a borrowed field has a slot of its own (#460) - #471

Merged
ASDAlexander77 merged 3 commits into
mainfrom
fix-rc-literal-field-read
Oct 3, 2026
Merged

ASDAlexander77 merged 3 commits into
mainfrom
fix-rc-literal-field-read

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Fixes #460.

Under rc, const o = { s: p.child } held no reference: overwriting p.child freed what o.s still pointed at.

An object literal hands its fields over without retaining them; the receiver is the one that retains them, as a let does through the record's retain routine. But a const initialised with the literal is folded into a read of the literal's temporary slot, so nothing retained the field.

processConstRef now gives identity storage (as #454 did for const b = a) to a const record read out whole from a literal's temporary slot, when one of its fields was given a value that carries no reference of its own:

  • a read of another object's field or element ({ s: p.child });
  • a read of an owning local ({ x: a });
  • a literal nested in this one.

The const then retains its fields as a let does, in every model. A literal of fresh values, constants or spread fields is unchanged.

Not covered: a spread of a local's record (const o2 = { ...o }) still holds o's fields without a reference under rc. Giving it storage makes own reject one corpus file (00spread_assignment), so it is left for its own change.

Tests

  • New 00const_object_literal_field.ts, registered as compile and JIT tests and in the rc/none corpus. It failed under rc before the change. It covers:

    • a literal of a field read;
    • a literal, and a nested one, of a local;
    • a loop.

    Each is read after allocations of the same size have reused anything freed.

  • The own corpus is unchanged.

  • Full Release ctest, on the branch merged with main: 3666/3667. The one failure is test-compile-gc-defaultlib-collector, the known local LNK2005 against the stale installed default library; it fails on main the same way.

🤖 Generated with Claude Code

ASDAlexander77 and others added 3 commits October 3, 2026 20:21
…#460)

Under rc, `const o = { s: p.child }` held nothing: overwriting `p.child` freed what `o.s` still
pointed at. An object literal hands its fields over unretained - its receiver is the one that
retains them, as a `let` does through the record's retain routine - but a `const` initialised with
the literal is folded, a read of the literal's temporary slot, so nothing retained the field.

processConstRef now gives identity storage (as for #454) to a const record read out whole from a
literal's temporary slot when one of its fields was given a value with no reference of its own: a
read of another object's field or element (`{ s: p.child }`), a read of an owning local
(`{ x: a }`), or a literal nested in this one. The const then retains its fields as a `let` does, in
every model. A literal of fresh values, constants or spread fields is unchanged.

Not covered: a spread of a local's record (`const o2 = { ...o }`) still holds `o`'s fields
unretained under rc; giving it storage turns one own corpus file (00spread_assignment) into an
error, so it is left for its own change.

Test: 00const_object_literal_field (compile and JIT, and in the rc/none corpus): a literal of a
field read, a literal (and a nested one) of a local, and a loop, each read after allocations of the
same size have reused anything freed. It failed under rc before the change. The own corpus is
unchanged.

Fixes #460.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linux CI failed 00object_boxed_infra under rc (compile and JIT, glibc abort): `const copy = { ...it }`
spreads a generator object, whose fields are a method bound to it (`next`) and a reference to its
capture box (`.captured`). Both are read out of `it`'s fields, so readsLiteralSlot counted the
literal as holding borrowed fields and gave `copy` storage and a record retain/release - which do
not pair up for a bound method, so the release freed what it did not hold. Windows' heap let it
pass.

Only a field whose type owns a block now counts, and a bound method does not: `copy` is folded
again, as on main, and `{ s: p.child }` still gets its storage.

Checked on Linux (WSL, GCC): 00object_boxed_infra and 00const_object_literal_field pass under
rc, none and the default model, compile and JIT.

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

Copy link
Copy Markdown
Owner Author

Linux CI failed 00object_boxed_infra under rc (glibc abort). const copy = { ...it } spreads a generator object whose fields are a bound method and a capture-box reference; the literal rule counted them and gave copy storage plus a record retain/release, which do not pair up for a bound method. Fixed in 7146544: only fields that own a block count, and a bound method does not, so copy is folded again as on main. Verified on Linux (WSL, GCC): 00object_boxed_infra and 00const_object_literal_field pass under rc/none/default, compile and JIT; Windows full suite 3666/3667 (known LNK2005), own corpus unchanged.

🤖 Generated with Claude Code

@ASDAlexander77
ASDAlexander77 merged commit 9a5cc05 into main Oct 3, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-rc-literal-field-read branch October 3, 2026 20:42
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.

-mm=rc: { s: p.child } takes no reference; overwriting p.child frees what the literal holds

1 participant