Skip to content

fix(object): symbol keys + accessor [[Get]] in descriptor/enumeration engine#5177

Merged
proggeramlug merged 3 commits into
mainfrom
fix/object-descriptor-cluster-v2
Jun 15, 2026
Merged

fix(object): symbol keys + accessor [[Get]] in descriptor/enumeration engine#5177
proggeramlug merged 3 commits into
mainfrom
fix/object-descriptor-cluster-v2

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Reduces the built-ins/Object test262 tail by fixing three shared spec-compliance gaps in the property-descriptor / enumeration engine, plus an own-symbol enumeration-order bug they exposed. Found by differential testing against node v26.

Root causes (each clears multiple tests)

  1. Object.getOwnPropertyDescriptors omitted symbol keys. It walked only getOwnPropertyNames (the string subset), so symbol-keyed descriptors were never reported. Now also enumerate getOwnPropertySymbols and install each descriptor under its symbol key; also skip undefined descriptors per spec.

  2. Object.entries / Object.values read raw index field slots. That (a) did not fire an own accessor's getter (returned the empty data slot) and (b) re-read the live keys_array, so a getter that added/removed/hid a future key during enumeration perturbed the result set. Now snapshot the enumerable key list once, then read each via the name-keyed [[Get]] (fires accessors), re-checking own-ness + enumerability at read time per EnumerableOwnProperties.

  3. Object.assign copied symbol values straight from the value side table. That missed accessor-only symbol properties entirely and never ran a symbol getter. Now iterate the full own-symbol-key list (includes accessors) and read each via [[Get]].

  4. Own symbol-key enumeration order. js_object_get_own_property_symbols appended accessor-only symbols from a HashMap in nondeterministic order, so a defineProperty(o, sym, {get}) pair came out reversed. Now sort the merged own-symbol set by the symbol's monotonic creation id (source order for symbols created and assigned in sequence) — the convention the class-ref symbol path already uses.

Results (scoped test262, node v26 oracle, before → after)

sub-area before after delta
Object/entries 13 17 +4
Object/values 12 16 +4
Object/getOwnPropertyDescriptors 13 16 +3
Object/assign 30 31 +1

No regressions

  • built-ins/Reflect: 97 pass / 5 fail — identical pristine vs fixed.
  • built-ins/Proxy: 207 pass / 33 fail — identical pristine vs fixed.
  • cargo test -p perry-runtime object::: 29 passed, 0 failed.

Remaining tail (separate roots, out of scope)

  • assign: readonly-target-throw edge, proxy trap-count tests, array-source index copy (Object.assign([7,8,9],[1])), proxy symbol-order.
  • entries/gOPD: Proxy ownKeys interaction (observable-operations).

No new codegen/dispatch entry points (all changes are inside existing #[no_mangle] runtime functions), so no API_MANIFEST change is required.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Object.assign to execute getters for symbol-keyed properties during symbol-keyed copy.
    • Fixed Object.getOwnPropertyDescriptors to reliably include descriptor entries for both string and symbol keys, omitting removed properties.
    • Fixed Object.values and Object.entries enumeration to better match ECMAScript behavior, including correct accessor invocation.
    • Improved ordering of own symbol properties for more deterministic results.

Ralph Küpper added 2 commits June 15, 2026 07:50
…n entries/values/assign

Three shared spec-compliance gaps in the property-descriptor / enumeration
engine, found via test262 built-ins/Object tail:

1. Object.getOwnPropertyDescriptors omitted symbol keys. It only walked
   getOwnPropertyNames (the string subset) so symbol-keyed descriptors were
   never reported. Now also enumerate js_object_get_own_property_symbols and
   install each descriptor under its symbol key; also skip undefined
   descriptors per spec. (symbols-included, order-after-define-property.)

2. Object.entries / Object.values read raw index field slots, which (a) did
   not fire an own accessor's getter (returned the empty data slot) and (b)
   re-read the live keys_array, so a getter that added/removed/hid a future
   key during enumeration perturbed the result set. Now snapshot the
   enumerable key list once, then read each via the name-keyed [[Get]]
   (fires accessors), re-checking own-ness + enumerability at read time per
   EnumerableOwnProperties. (entries/getter-adding-key,
   getter-removing-future-key, getter-making-future-key-nonenumerable.)

3. Object.assign copied symbol values straight from the value side table,
   which (a) missed accessor-only symbol properties entirely and (b) never
   ran a symbol getter. Now iterate the full own-symbol-key list
   (getOwnPropertySymbols, includes accessors) and read each via [[Get]].
   (assign/strings-and-symbol-order.)
js_object_get_own_property_symbols appended accessor-only symbols (from a
HashMap) in nondeterministic order, so a defineProperty(o, sym, {get}) pair
came out reversed. Sort the merged own-symbol set by the symbol's monotonic
creation id (source order for symbols created and assigned in sequence) — the
same convention the class-ref symbol path uses. Fixes the symbol-key ordering
observed by getOwnPropertyDescriptors and Object.assign.
@coderabbitai

coderabbitai Bot commented Jun 15, 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: 290b5760-e84d-462e-863e-f1d24b458c6d

📥 Commits

Reviewing files that changed from the base of the PR and between 739d57d and a2ea46d.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/object/field_get_set.rs
  • crates/perry-runtime/src/symbol.rs

📝 Walkthrough

Walkthrough

Four property-enumeration functions in the runtime are updated to match ECMA-262 semantics: js_object_values and js_object_entries adopt a snapshot-then-[[Get]] pattern; js_object_assign_one fetches symbol-keyed values via [[Get]] instead of cloned side-table bits; js_object_get_own_property_descriptors gains symbol-key descriptor support and filters out undefined descriptors; and js_object_get_own_property_symbols now sorts accessor-only results by symbol creation id.

Changes

ECMA-262 Property Enumeration Compliance

Layer / File(s) Summary
Symbol key stable ordering
crates/perry-runtime/src/symbol.rs
js_object_get_own_property_symbols preserves insertion order of data-valued symbol entries, then sorts the accessor-only tail by each symbol's monotonic creation id (null pointers treated as u64::MAX), providing deterministic order for all callers.
Object.values and Object.entries: snapshot + [[Get]]
crates/perry-runtime/src/object/field_get_set.rs
Both functions switch to a two-phase pattern: snapshot enumerable keys first (skipping hidden/non-enumerable fields), then re-validate own presence/enumerability and fetch values via js_object_get_field_by_name ([[Get]]) so accessor getters fire correctly and mutating getters cannot perturb the snapshot.
Object.assign: symbol-keyed [[Get]] path
crates/perry-runtime/src/object/alloc.rs
Replaces direct clone_symbol_entries_for_obj_ptr cloning with a snapshot of owned symbol pointers via js_object_get_own_property_symbols, then reads each value through js_object_get_symbol_property ([[Get]]) to invoke symbol-keyed accessor getters, matching the existing string-key path behavior.
Object.getOwnPropertyDescriptors: symbol keys and undefined filter
crates/perry-runtime/src/object/descriptors.rs
Skips string-key entries whose descriptor lookup returns TAG_UNDEFINED, then adds a second pass that enumerates own symbol keys, computes descriptors, skips undefined results, and installs remaining descriptors via symbol-key setters before returning the result object.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#5146: Both PRs modify Perry's Object.assign symbol handling in crates/perry-runtime/src/object/alloc.rs, so they directly interact at the assign code level.

Poem

🐇 Hop, hop, the symbols align,
Sorted by id — now they're fine!
Snapshot the keys, then [[Get]] each one,
Accessors fire before we're done.
No stale descriptor shall sneak through,
ECMA-262 gets its due! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main changes: fixing symbol key handling and accessor [[Get]] behavior in the descriptor and enumeration engine across multiple object operations.
Description check ✅ Passed The description comprehensively covers all required sections: a clear summary, detailed root causes with explanations, test results showing improvements, and verification of no regressions. It addresses the template requirements fully.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/object-descriptor-cluster-v2

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-runtime/src/object/field_get_set.rs`:
- Around line 1999-2016: The code currently stores key pointers as NaN-boxed f64
values in a heap-allocated Vec<snapshot_keys>, which is not visible to the GC as
a root. When a getter is invoked during the subsequent key iteration, it can
trigger GC and potentially collect keys that are only referenced through the
heap buffer. To fix this, either snapshot the owned key bytes (store the actual
string data instead of just pointers) or explicitly root the key handles before
invoking any getters in the loop that processes keys. This pattern appears at
multiple sites in the file: the main location at lines 1999-2016 where keys are
collected and then iterated to call getters, and also at lines 2030, 2173-2191,
and 2217 where similar patterns exist. Apply the same fix pattern across all
these locations to ensure all key references remain visible to the GC throughout
getter invocations.
- Around line 2009-2012: The enumerability check using
descriptor_marks_non_enumerable is being performed during the snapshot phase,
which is too early and misses cases where getters can change what properties
become enumerable after enumeration has started. Remove the filtering condition
that skips non-enumerable keys from being added to snapshot_keys in
EnumerableOwnProperties, and instead defer the enumerability check to the
per-key read phase when each key is actually visited. This ensures that
descriptor changes caused by getter execution during enumeration are properly
reflected. Apply the same refactoring to all similar snapshot-phase filtering
patterns in the codebase where enumerability checks are currently being done
prematurely on cached descriptor information.

In `@crates/perry-runtime/src/symbol.rs`:
- Around line 2324-2340: The unconditional sort by symbol-creation ID is
reordering all entries including data symbols that are already in correct
insertion order, which violates the own-property-keys guarantee. Instead of
sorting the entire entries list, partition it into two groups: data-valued
symbols (which maintain their insertion order from the entries list) and
accessor-only symbols (which come from the nondeterministic HashMap). Sort only
the accessor-only symbols by their SymbolHeader ID to establish a deterministic
order, then recombine by placing data-valued symbols first followed by the
sorted accessor-only symbols. This preserves insertion order for existing
properties while fixing the nondeterministic ordering of accessor-only symbols
added via defineProperty.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 43084b56-1a6b-4cd5-8abd-954f5c71e67f

📥 Commits

Reviewing files that changed from the base of the PR and between 08819f0 and 739d57d.

📒 Files selected for processing (4)
  • crates/perry-runtime/src/object/alloc.rs
  • crates/perry-runtime/src/object/descriptors.rs
  • crates/perry-runtime/src/object/field_get_set.rs
  • crates/perry-runtime/src/symbol.rs

Comment thread crates/perry-runtime/src/object/field_get_set.rs Outdated
Comment thread crates/perry-runtime/src/object/field_get_set.rs Outdated
Comment thread crates/perry-runtime/src/symbol.rs
…data-symbol order

Address CodeRabbit review on #5177:

- field_get_set.rs (js_object_values / js_object_entries): snapshot own keys
  as owned bytes instead of NaN-boxed pointers. A getter fired by
  js_object_get_field_by_name can delete a future key and GC before we visit
  it; a key kept only inside the Rust-heap Vec is not a stack-visible GC root,
  so it could dangle. Rematerialize the string at read time instead.

- Move the enumerability filter entirely to the per-key read phase (drop the
  cached has_descriptors / snapshot-phase skip): per EnumerableOwnProperties an
  earlier getter can create a descriptor or flip a future key's enumerability,
  which the up-front filter and stale has_descriptors cache both missed.

- symbol.rs (js_object_get_own_property_symbols): don't sort the whole merged
  set by creation id — that reordered data symbols already in insertion order
  (e.g. obj[b]=…; obj[a]=… with a created before b). Partition into
  data-valued (kept in insertion order) and the appended accessor-only tail
  (sorted by creation id for determinism).

Validated vs node v26: Object.values/entries getter-add/remove/enumerability
cases and data/accessor symbol ordering all match byte-for-byte. perry-runtime
object:: tests 29/29.
@proggeramlug proggeramlug merged commit 338d885 into main Jun 15, 2026
14 of 15 checks passed
@proggeramlug proggeramlug deleted the fix/object-descriptor-cluster-v2 branch June 15, 2026 07:59
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