Skip to content

fix(gc): re-read js_put_value_set operands after the key coercion (#8495) - #8499

Merged
proggeramlug merged 2 commits into
mainfrom
fix/8495-putvalue-stale-after-coercion
Aug 21, 2026
Merged

fix(gc): re-read js_put_value_set operands after the key coercion (#8495)#8499
proggeramlug merged 2 commits into
mainfrom
fix/8495-putvalue-stale-after-coercion

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Refs #8495 — a real GC correctness bug, isolated to a 16-line reproduction.

js_put_value_set rooted target/key/value/receiver in a RuntimeHandleScope and then immediately bound all four to plain f64 locals — before calling js_to_property_key:

let value = value_handle.get_nanbox_f64();      // pre-collection address
...
let property_key_handle = scope.root_nanbox_f64(js_to_property_key(key));  // runs user code, evacuates

ToPropertyKey invokes user toString / Symbol.toPrimitive, which allocates and can move objects. The handles keep them alive, but the locals captured pre-collection addresses, so every path below the coercion operated on stale pointers. This is the same shape as #8427/#8428: correct rooting defeated by binding the raw pointer too early.

Symptom: o[heavyKey("k")] = payload() reads back undefined one collection later — the #6935 contract, re-broken from the other side.

Isolation (both under PERRY_GC_FORCE_EVACUATE=1):

  • static key → survives ✅
  • same write with an allocating key coercion → LOST ❌ → fixed → survives ✅

Result on gc_property_key_operand_rooting_6935: 1/3 → 2/3.

  • proxy_target_set_survives_forced_evacuation — now ok
  • property_key_receivers_survive_forced_evacuation — still ok
  • property_key_stored_values_survive_forced_evacuation — its dyn-index-set case now passes; the Symbol.toPrimitive flavour still fails and routes through js_object_set_index_polymorphic rather than this function, so it needs the same audit there. Tracked in GC: this holds a stale from-space pointer after a moving collection inside a method body #8495 — this PR does not claim to close it.

Checks: perry-runtime proxy:: (17) and object:: (201) unit tests pass; cargo fmt clean.

Worth noting for reviewers: this bug was invisible for months because cargo-test stops at its first failing suite and the bun_ffi_stage1 abort (#8479) sat ahead of it. full-suite-gate has been red since June.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed property assignments involving proxies and dynamic indexes that could behave incorrectly after garbage collection.
    • Improved stability when property-key conversion triggers memory allocation or object movement.
    • Ensured assignment targets, values, and receivers are read safely after property keys are processed.
    • Prevented stale data from causing incorrect results during certain dynamic property assignments.

The function rooted target/key/value/receiver in a RuntimeHandleScope and
then bound all four to plain f64 locals BEFORE calling js_to_property_key.
That call runs user code (toString / Symbol.toPrimitive) which allocates
and can evacuate: the handles keep the objects ALIVE, but the locals hold
pre-collection addresses, so every path below the coercion operated on
stale pointers. Rooting the caller could not help — this is the same shape
as #8427/#8428, where a correctly-rooted value is defeated by binding the
raw pointer too early.

Symptom: o[heavyKey("k")] = payload() reads back undefined one collection
later — exactly the #6935 contract, re-broken from the other side.

Isolated to a 16-line reproduction: a static key survives, the same write
with an allocating key coercion loses the value.

gc_property_key_operand_rooting_6935 goes 1/3 -> 2/3:
  proxy_target_set_survives_forced_evacuation ......... now ok
  property_key_receivers_survive_forced_evacuation .... still ok
  property_key_stored_values_survive_forced_evacuation  dyn-index-set now
      passes; its Symbol.toPrimitive flavour still fails and routes through
      js_object_set_index_polymorphic instead — tracked in #8495.

perry-runtime proxy:: (17) and object:: (201) unit tests pass.

Refs #8495
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f9b9df6-8d91-4be3-8e44-08ee33c78e18

📥 Commits

Reviewing files that changed from the base of the PR and between eb563f5 and bfaba90.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • CLAUDE.md
  • Cargo.toml

📝 Walkthrough

Walkthrough

js_put_value_set now performs property-key coercion before reading operands from GC handles. This prevents stale pointers after object evacuation. The changelog and project version references document the update.

Changes

PutValue stale operand fix

Layer / File(s) Summary
Reorder key coercion and operand reads
crates/perry-runtime/src/proxy/put_value.rs, changelog.d/8496-putvalue-stale-operands.md, Cargo.toml, CLAUDE.md
js_put_value_set converts the property key before reading target, value, and receiver. The changelog documents the affected cases. Project version references change from 0.5.1512 to 0.5.1513.

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

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the GC fix in js_put_value_set and the operand re-read after key coercion.
Description check ✅ Passed The description explains the bug, fix, affected paths, test results, remaining issue, and related issue with sufficient technical detail.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (1 skipped: 1 unsupported.)
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/8495-putvalue-stale-after-coercion

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
proggeramlug merged commit 4091d51 into main Aug 21, 2026
8 checks passed
@proggeramlug
proggeramlug deleted the fix/8495-putvalue-stale-after-coercion branch August 21, 2026 01:07
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