fix(runtime): #5552 demote unique strings in remaining array insert paths (unshift / fill / with / from_jsvalue)#5567
Conversation
…aths Follow-up to #5533 (object fields) and #5548 (the array store paths it enumerated). A uniquely-owned (refcount==1) heap string written into an array element aliases that slot, so a later in-place `s += x` on the source local (`js_string_append`'s refcount==1 fast path) rewrites the stored element and corrupts it. #5548 fixed the push / set / from_values / splice-insert paths; the sibling insert/replace paths below do the same raw element write without the demote. Apply the same tag-checked `js_string_addref_if_heap_string` (no-op for SSO / non-string, idempotent) before the element write at each: - `js_array_unshift_f64` (covers `js_array_unshift_jsvalue` transitively) and the per-item loop in `js_array_unshift_variadic`. - `js_array_fill` / `js_array_fill_range` (demote once before the fill loop — the source aliases every filled slot) and `js_array_fill_generic` (its object-receiver loop writes `value` into each index directly; the array receiver delegates to the two above, so the extra demote is idempotent). - `js_array_with` (the replacement value stored into the new array's slot; the cloned elements are already shared). - `js_array_from_jsvalue` (mixed-type literal construction — the JSValue sibling of the already-covered `js_array_from_values`). Internal reshuffles (sort, splice tail shift, slice copy, copyWithin) only move values already stored in an array — already shared — so no demote is needed. Tests: `string_append_heap_alias.rs` gains compile-run regressions for unshift / fill / with, each confirmed to fail without the demote. `js_array_from_jsvalue` is not emitted by codegen from any TypeScript source, so it gets a runtime unit test (`array/tests.rs`) instead, also confirmed to fail without the demote. Refs #5533, #5548. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughSeven array mutation functions across four runtime files now call ChangesHeap-string demotion in array insert/replace paths
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…t write The #5552 demote line pushed the ptr::write past the proximity window of the existing GC_STORE_AUDIT(BARRIERED) marker, failing the lint job's GC store-site inventory check. Re-annotate the insert write directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes #5552. Follow-up to #5533 and #5548.
The bug
A uniquely-owned (
refcount==1) heap string written into an array element aliases that slot, so a later in-places += xon the source local (js_string_append's refcount==1 fast path) rewrites the stored element and corrupts it. #5548 demoted the string at the array store paths it enumerated (push/set/from_values/ inline element store / splice-insert); this PR covers the sibling insert/replace paths it left uncovered.The fix
Add the tag-checked
js_string_addref_if_heap_stringdemote (no-op for SSO / non-string, idempotent) before the element write at each remaining path:js_array_unshift_f64— coversjs_array_unshift_jsvaluetransitively — and the per-item loop injs_array_unshift_variadic.js_array_fill/js_array_fill_range(demote once before the fill loop — the source otherwise aliases the source local and every filled slot) andjs_array_fill_generic(its object-receiver loop writesvalueinto each index directly; the array receiver delegates to the two above, so the extra demote is idempotent).js_array_with— the replacement value stored into the new array's slot. The cloned elements come from the source array and are already shared.js_array_from_jsvalue— mixed-type literal construction, the JSValue sibling of the already-coveredjs_array_from_values.Internal reshuffles (sort, splice tail shift, slice copy, copyWithin) only move values already stored in an array — already shared — so no demote is needed (same reasoning as #5548).
Tests
crates/perry/tests/string_append_heap_alias.rsgains compile-run regressions for unshift / fill / with, each verified to fail without the demote and pass with it.js_array_from_jsvalueis not emitted by codegen from any TypeScript source (no compiled-TS test can reach it), so it gets a runtime unit test incrates/perry-runtime/src/array/tests.rs, also verified to fail without the demote.All 12 integration tests + the runtime unit test pass with the fix.
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
fill(),with(), andunshift().Tests
with()) to ensure array-stored string values remain stable after in-place+=appends.