You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sibling of #7570 (fixed by #7573) on a different family. Filed from the sweep
that PR did of every declared-type-keyed lowering; not fixed there because it is
a separate set of code paths with a different guard already half-present.
Shape
class X extends Array produces a plain ObjectHeader, not an ArrayHeader — stated outright in crates/perry-runtime/src/array/subclass.rs:1-7
("perry has no exotic array-object representation"), and js_array_subclass_init
(node_stream_constructors/builders.rs:98) installs the elements as ordinary
object properties, with no ArrayHeader backing (unlike js_map_set_subclass_init, which does allocate a real hidden Map/Set).
ObjectHeader and ArrayHeader overlay:
ArrayHeader
offset
reads on an ObjectHeader
length: u32
0
object_type (= 1)
capacity: u32
4
class_id
(elements begin)
8
parent_class_id ‖ field_count, then keys_array, then meta
is_array_expr (crates/perry-codegen/src/type_analysis/predicates.rs:401) is
satisfied by a declaredType::Array(_) / Generic { base: "Array" }, so a
binding annotated with the base type and holding a subclass instance takes the
raw lowering — the same "a declared type is a hint, never a layout fact" premise
as #7570.
crates/perry-codegen/src/expr/index_get.rs:531 — lower_bounded_array_index_get
(the hoisted-arr.length loop fast path) tests only GC_TYPE_LAZY_ARRAY, GC_FLAG_FORWARDED and OBJ_FLAG_ARRAY_DESCRIPTORS, then does a raw gep + load double at handle + 8 + idx*8 — straight into parent_class_id ‖ field_count, then keys_array, then meta.
crates/perry-codegen/src/expr/property_get.rs:236 — inline arr.length is a
bare safe_load_i32_from_ptr(recv) at offset 0, no GC-type test, so it reports object_type (= 1).
crates/perry-codegen/src/expr/index_set.rs — the guard-free Ptr<NumArray> store arm (try_lower_num_array_guard_free_set).
The tier that already does it right
crates/perry-codegen/src/expr/index_get/guarded_array.rs:22-100 loads the GC
header byte at handle - 8 and tests icmp eq i8 %gc_type, 1 (GC_TYPE_ARRAY)
before any slot load, branching to a generic fallback otherwise. An Array
subclass instance takes the fallback there and behaves correctly.
The recognition machinery also already exists on the runtime side and is used by js_native_call_method — array::subclass::is_array_subclass_instance (:46,
which requires GC_TYPE_OBJECT via try_read_gc_header) and array_subclass_dense_snapshot (:77, used by iteration/spread) — so class X extends Array works fine through the generic dispatch. It is only the
declared-type fast paths that are unsafe.
extend the guarded_array.rsGC_TYPE_ARRAY test to the other tiers (keeps
the fast path, costs one header-byte load + branch), or
give the raw js_array_* entries a receiver resolution the way fix(runtime): a Map/Set subclass in a base-typed binding was read as a raw header (#7570) #7573 gave clean_map_ptr / clean_set_ptr one, so it is fail-closed for every future
caller. Note this family has no hidden backing to redirect to — an Array
subclass keeps its elements as object properties — so the runtime-side answer
is "fall back to the generic array-like path", not "redirect".
The second is the one that cannot be forgotten at a new call site; the first is
the smaller diff.
Sibling of #7570 (fixed by #7573) on a different family. Filed from the sweep
that PR did of every declared-type-keyed lowering; not fixed there because it is
a separate set of code paths with a different guard already half-present.
Shape
class X extends Arrayproduces a plainObjectHeader, not anArrayHeader— stated outright incrates/perry-runtime/src/array/subclass.rs:1-7("perry has no exotic array-object representation"), and
js_array_subclass_init(
node_stream_constructors/builders.rs:98) installs the elements as ordinaryobject properties, with no
ArrayHeaderbacking (unlikejs_map_set_subclass_init, which does allocate a real hidden Map/Set).ObjectHeaderandArrayHeaderoverlay:ArrayHeaderObjectHeaderlength: u32object_type(= 1)capacity: u32class_idparent_class_id‖field_count, thenkeys_array, thenmetais_array_expr(crates/perry-codegen/src/type_analysis/predicates.rs:401) issatisfied by a declared
Type::Array(_)/Generic { base: "Array" }, so abinding annotated with the base type and holding a subclass instance takes the
raw lowering — the same "a declared type is a hint, never a layout fact" premise
as #7570.
Tiers that do NOT brand-check
crates/perry-codegen/src/expr/index_get.rs:531—lower_bounded_array_index_get(the hoisted-
arr.lengthloop fast path) tests onlyGC_TYPE_LAZY_ARRAY,GC_FLAG_FORWARDEDandOBJ_FLAG_ARRAY_DESCRIPTORS, then does a rawgep+load doubleathandle + 8 + idx*8— straight intoparent_class_id ‖ field_count, thenkeys_array, thenmeta.crates/perry-codegen/src/expr/property_get.rs:236— inlinearr.lengthis abare
safe_load_i32_from_ptr(recv)at offset 0, no GC-type test, so it reportsobject_type(= 1).crates/perry-codegen/src/lower_call/property_get.rs:215→lower_array_method(
js_array_pop_f64,js_array_join,js_array_sort,js_array_reverse, …).crates/perry-codegen/src/expr/property_set.rs:383→js_array_set_length_strict.crates/perry-codegen/src/expr/index_set.rs— the guard-freePtr<NumArray>store arm (try_lower_num_array_guard_free_set).The tier that already does it right
crates/perry-codegen/src/expr/index_get/guarded_array.rs:22-100loads the GCheader byte at
handle - 8and testsicmp eq i8 %gc_type, 1(GC_TYPE_ARRAY)before any slot load, branching to a generic fallback otherwise. An Array
subclass instance takes the fallback there and behaves correctly.
The recognition machinery also already exists on the runtime side and is used by
js_native_call_method—array::subclass::is_array_subclass_instance(:46,which requires
GC_TYPE_OBJECTviatry_read_gc_header) andarray_subclass_dense_snapshot(:77, used by iteration/spread) — soclass X extends Arrayworks fine through the generic dispatch. It is only thedeclared-type fast paths that are unsafe.
Suggested fix
Whichever of the two #7573 considered:
guarded_array.rsGC_TYPE_ARRAYtest to the other tiers (keepsthe fast path, costs one header-byte load + branch), or
js_array_*entries a receiver resolution the way fix(runtime): a Map/Set subclass in a base-typed binding was read as a raw header (#7570) #7573 gaveclean_map_ptr/clean_set_ptrone, so it is fail-closed for every futurecaller. Note this family has no hidden backing to redirect to — an Array
subclass keeps its elements as object properties — so the runtime-side answer
is "fall back to the generic array-like path", not "redirect".
The second is the one that cannot be forgotten at a new call site; the first is
the smaller diff.