Skip to content

fix(runtime): structuredClone keeps overflow/dynamic properties (#4879)#5052

Merged
proggeramlug merged 1 commit into
mainfrom
fix/structured-clone-overflow-props
Jun 13, 2026
Merged

fix(runtime): structuredClone keeps overflow/dynamic properties (#4879)#5052
proggeramlug merged 1 commit into
mainfrom
fix/structured-clone-overflow-props

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Fixes #4879.

Root cause

Exactly the suspected mechanism: the GC_TYPE_OBJECT branch of js_structured_clone_inner (builtins/globals.rs) clones via js_object_clone_with_extra, which copies only the inline field_count slots and truncates the keys array to min(keys_len, field_count). Anything in the OVERFLOW_FIELDS side table — or every property of a {}-born object, which has no inline capacity at all (explains the issue's "even f0 is undefined at 50 props" wrinkle: field_count is 0 there, not 8) — vanished from the clone.

Fix

When keys_array.length > field_count, rebuild the clone key-by-key: js_object_get_field resolves inline-vs-overflow per index, values recurse through js_structured_clone, and js_object_set_field_by_name writes into a fresh object. RuntimeHandleScope roots src/clone across the recursive deep-clone (it can trigger minor GCs), and key bytes are owned before recursing. Fully-inline objects keep the existing clone_with_extra fast path untouched.

Validation

Perry output now matches node --experimental-strip-types exactly on:

  • the issue repro (50 dynamic props: f0/f49/key count all correct)
  • 3000 dynamic props: 0 missing (was 2992)
  • literal round-trip + deep-copy independence (mutating clone leaves source alone)
  • small {}-born objects, nested dynamic-object-inside-array-inside-literal
  • New unit test structured_clone_keeps_overflow_properties (50-prop {}-born object); the existing structured-clone GC rooting test still passes.

Code-only PR — version bump + changelog left for merge time.

The GC_TYPE_OBJECT branch of js_structured_clone_inner cloned via
js_object_clone_with_extra, which copies only the inline field_count
slots and truncates the keys array to match. Properties living outside
the inline region — the OVERFLOW_FIELDS side table of a dict-grown
object, or every prop of a {}-born object with no inline capacity —
silently vanished from the clone (50 dynamic props cloned to {}, 3000
lost all but 8).

When the keys array is longer than the inline region, rebuild the clone
key-by-key via js_object_get_field (resolves inline vs overflow per
index) + js_object_set_field_by_name, with RuntimeHandleScope rooting
across the recursive deep-clone (it can run minor GCs). Fully-inline
objects keep the existing fast path.
@proggeramlug proggeramlug merged commit cf3c470 into main Jun 13, 2026
13 checks passed
@proggeramlug proggeramlug deleted the fix/structured-clone-overflow-props branch June 13, 2026 04:20
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.

structuredClone drops dynamically-added (overflow) properties of plain objects

1 participant