Skip to content

test(gc): a zero-slot test fixture has room for the named-store floor (fixes #10941) - #10949

Closed
proggeramlug wants to merge 1 commit into
mainfrom
fix/10941-zero-slot-fixture-floor
Closed

proggeramlug wants to merge 1 commit into
mainfrom
fix/10941-zero-slot-fixture-floor

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Fixes #10941.

The bug

alloc_{nursery,old}_test_object(0) (gc/tests/support.rs) allocated exactly an ObjectHeader and left the receiver unstamped, on the reasoning recorded in the comment above it:

A zero-slot fixture needs no descriptor at all — the derived bound is 0 either way — and minting one would perturb the descriptor-count accounting that sibling tests assert on.

A named-property write does not respect that bound. The inline/overflow boundary is max(object_live_slot_count(obj), INLINE_SLOT_FLOOR) and the floor is 2, so the first two keys written to a zero-slot fixture store into inline slots 0 and 1 of an object that has none. Those two words are the next cell.

Every caller before PR #10938 only ever set a [[Prototype]] on one, so nothing had written a named property and the hazard was invisible. It presents as a wrong read now and a SIGSEGV somewhere unrelated later.

The change

Option 1 from the issue. Both fixtures allocate max(field_count, INLINE_SLOT_FLOOR) slots while publishing the bound as field_count, so the collector still traces exactly field_count slots and the descriptor-count accounting the original comment protects is unchanged. The zero-fill covers the allocation rather than the published bound.

Witness, and it can fail

gc::tests::zero_slot_fixture asserts the allocation — for the nursery fixture and its old-generation twin — and reverting the change reddens both by name:

a named-property write on this fixture stores into inline slots 0 and 1 — the
store path's floor is `max(object_live_slot_count(obj), INLINE_SLOT_FLOOR)` and
never zero — but the allocation carries 16 payload bytes against the 32 it
would need. Those words are the NEXT CELL (#10941).

An end-to-end pin — six named writes to a zero-slot fixture, read back — was written and then deliberately dropped, and the module doc says so: without the fix it does not fail, it dumps core, which under --test-threads=1 takes the other ~4,200 results in the process with it. The structural assertions redden with a message and for the same reason.

Suite

cargo test --release -p perry-runtime --lib -- --test-threads=1, both arms:
4218 passed / 0 failed / 6 ignored.

Base: 0fa391529 (train 253, v0.5.1633). Independent of #10948 (#10939) — either can land first.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed zero-slot garbage-collector test objects so named-property writes no longer overwrite adjacent memory.
    • Ensured both nursery and old-generation test allocations reserve the minimum inline storage required.
  • Tests

    • Added coverage verifying minimum payload allocation for zero-slot objects in both allocation generations.

…fixes #10941)

`alloc_{nursery,old}_test_object(0)` allocated exactly an `ObjectHeader` and
left the receiver unstamped, on the reasoning recorded above it that "a
zero-slot fixture needs no descriptor at all - the derived bound is 0 either
way".

A named-property write does not respect that bound. The inline/overflow
boundary is `max(object_live_slot_count(obj), INLINE_SLOT_FLOOR)` and the
floor is 2, so the first two keys written to a zero-slot fixture store into
inline slots 0 and 1 of an object that has none. Those two words are the next
cell. Every caller before PR #10938 only ever set a `[[Prototype]]` on one,
so nothing had written a named property and the hazard was invisible; it
presents as a wrong read now and a SIGSEGV somewhere unrelated later.

Both fixtures now allocate `max(field_count, INLINE_SLOT_FLOOR)` slots while
PUBLISHING the bound as `field_count`, so the collector still traces exactly
`field_count` slots and the descriptor-count accounting the original comment
protects is unchanged.

Witness: `gc::tests::zero_slot_fixture` asserts the ALLOCATION, for both the
nursery and the old-generation fixture, and reddens by name when the change
is reverted. An end-to-end pin - six named writes, read back - was written
and deliberately dropped: without the fix it does not fail, it dumps core,
which under `--test-threads=1` takes the other ~4,200 results with it. That
is recorded in the module doc.

Suite: 4218 passed / 0 failed / 6 ignored, `--test-threads=1`, both arms.
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: a7ab6eb8-2912-4ed0-8b1c-9efd6a580b3d

📥 Commits

Reviewing files that changed from the base of the PR and between 0fa3915 and 49d232f.

📒 Files selected for processing (3)
  • crates/perry-runtime/src/gc/tests/mod.rs
  • crates/perry-runtime/src/gc/tests/support.rs
  • crates/perry-runtime/src/gc/tests/zero_slot_fixture.rs

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The GC test allocators now reserve two physical inline slots for zero-slot objects. New nursery and old-generation tests verify the required payload size while preserving the published zero-slot bound.

Changes

Zero-slot GC fixture safety

Layer / File(s) Summary
Reserve inline storage
crates/perry-runtime/src/gc/tests/support.rs
Both test object allocators initialize at least INLINE_SLOT_FLOOR physical slots while keeping field_count as the published slot bound.
Validate zero-slot payloads
crates/perry-runtime/src/gc/tests/zero_slot_fixture.rs, crates/perry-runtime/src/gc/tests/mod.rs
New nursery and old-generation tests calculate the payload floor and verify that zero-slot fixtures meet it. The test module is registered in the GC test module.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 49d23

This test-only change reserves inline storage for zero-slot GC fixtures while preserving their published bounds; no merge-blocking production impact is evident.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the GC test-fixture change and its purpose. It is concise and directly related to the main changeset.
Description check ✅ Passed The description explains the bug, the allocation fix, preserved behavior, added tests, issue reference, and test results. It does not reproduce every template heading or checklist item, but it provide…
Linked Issues check ✅ Passed The PR meets the coding requirements in [#10941]. alloc_nursery_test_object and alloc_old_test_object allocate at least INLINE_SLOT_FLOOR physical slots for zero-slot fixtures and initialize tho…
Out of Scope Changes check ✅ Passed The changes stay within [#10941]. The fixture updates prevent the named-property overwrite, and the two structural tests provide direct regression coverage. The test-module registration and explanator…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Landed on main in merge train 255 (#10950, v0.5.1636), main c7cbc3c73b.

The train carried this PR at head 49d232fab9. The landed tree is byte-identical to the validated train tree (d43bd23008), and CI on the train head passed every job except the known public-baseline lint step. Trains rebase-merge, which gives new commit SHAs, so GitHub can't mark this PR merged. It's closed as landed.

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.

gc/tests/support.rs: alloc_{nursery,old}_test_object(0) allocates ZERO inline slots — a named-property write lands in the next cell

2 participants