Skip to content

gc: array_from_spread_value's Symbol.iterator lookup derefs a from-space object (inlined [...obj.arr]) #7498

Description

@proggeramlug

Found while building the regression witness for #7475 / #7495. Distinct from the js_iterator_to_array bug #7495 fixes, and present on both link modes, so it is not an auto-optimize artifact.

What

[...obj.arr] has two lowerings. When the spread's enclosing function stays out of line it calls js_iterator_to_array directly — that path is the one #7495 rooted. When the function is small enough to inline, the spread routes through array_from_spread_value instead, which first resolves [Symbol.iterator] via js_object_get_symbol_property. A stale from-space object is dereferenced inside THAT prototype walk.

Repro

Take #7495's witness test-files/test_gap_gc_iterator_drain_rooting.ts and shrink deepClone until it inlines — e.g. drop name / meta.created / meta.updated so the body is just { id, meta: { tags: [...o.meta.tags] } }. On #7495's branch (i.e. with the iterator drain already fixed):

PERRY_GC_PROTECT_FROMSPACE=1 PERRY_GC_PROTECT_FROMSPACE_DEPTH=200 ./out
[gc-fromspace-protect] FAULT: signal 10 at 0x313baa409a8
  block=0x313baa10000 +199080 retired_bytes=1048552 retired_by_minor=#0
  last-known object: user_ptr=0x313baa409b0 obj_type=2 size=72
2  js_object_get_field_by_name + 132
3  perry_runtime::object::field_get_set::fetch_subclass_handle_id + 112
4  perry_runtime::object::field_get_set::get_field_by_name_tail::get_field_by_name_object_tail + 13956
5  perry_runtime::object::field_get_set::accessors::default_object_prototype_property_value + 276
6  perry_runtime::object::field_get_set::accessors::array_prototype_property_value + 592
7  perry_runtime::object::field_get_set::get_field_by_name_tail::get_field_by_name_object_tail + 14464
8  js_object_get_field_by_name_f64 + 244
9  js_object_get_symbol_property + 2496
10 perry_runtime::array::iterator::array_from_spread_value + 1200
11 perry_fn_..._run + 1148

Without the instrument it surfaces as TypeError: next is not a function / TypeError: value is not a function, the #7341-family signature.

First thing to check

Per CLAUDE.md: "an unrooted register goes bad only when a collection lands in its window, so it is intermittent; an unrooted cache goes bad at collection #0 and stays bad, so a perfectly reproducible GC bug means a table, not a register." This one is perfectly reproducible and the retiring minor is #0, so start at the side tables the prototype walk reads, not at a register in array_from_spread_value. crates/perry-runtime/src/gc/tests/runtime_roots/prototype_addr_cache.rs already exists for one such cache; fetch_subclass_handle_id is the frame that actually faults.

Second candidate, if the tables come back clean: array_from_spread_value reads raw_ptr from its value argument near the top and then carries it through ~a dozen classification probes (entries_array_for_small_handle_id, is_registered_buffer, subclass_backing_for_default_iteration, array_subclass_dense_snapshot, try_read_as_search_params, the symbol lookup, …), several of which allocate. Neither value nor raw_ptr is rooted.

Scope note

Both links fail identically, so unlike #7475's headline symptom this is not sensitive to the auto-optimize feature set — every user is exposed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions