Skip to content

Map/Set indexed reads return NaN after array pointer hardening #8060

Description

@proggeramlug

Regression

At exact current main ef6e111a4fb85dcc9fd81f69c30bf57a0bf40a61, indexed reads from live Map/Set receivers routed through array-like runtime fallbacks return NaN. Six pre-existing tests fail individually and deterministically:

  • array::collection_tag_tests::a_live_map_receiver_still_reports_its_size_through_the_registry: NaN, expected map key 1.0
  • array::collection_tag_tests::a_live_set_receiver_still_reads_its_elements_through_the_registry: NaN, expected set value 5.0
  • value::dyn_index::collection_tag_tests::collection_receivers_still_use_their_authoritative_registries: NaN, expected map key 1.0
  • typed_feedback::tests::typed_feedback_boxed_fallback_preserves_fractional_keys_for_array_like_receivers: NaN, expected set value 20.0
  • typed_feedback::tests::runtime_dynamic_index_fallbacks_preserve_fractional_keys_for_array_like_receivers: NaN, expected set value 20.0
  • typed_feedback::tests::polymorphic_index_fallbacks_preserve_fractional_keys_for_array_like_receivers: NaN, expected set value 20.0

For example:

cargo test -p perry-runtime --lib \
  array::collection_tag_tests::a_live_map_receiver_still_reports_its_size_through_the_registry \
  -- --exact

Causal proof

This is an ordering regression introduced by #8041 (971d6ffb6b10c04724cd28d9ee6da2d750c84420), not collection-registry lifetime or GC rooting:

  1. fix(gc): install array growth forwarding for low-address arenas #8041 correctly hardened clean_arr_ptr to return null for every tracked object whose GcHeader::obj_type != GC_TYPE_ARRAY.
  2. js_array_get_f64 calls clean_arr_ptr(arr) before its existing GC_TYPE_MAP / GC_TYPE_SET branches.
  3. A genuine live Map or Set is therefore rejected at the clean funnel and returns NaN; its collection tag and authoritative registry can never be consulted.
  4. js_array_length is the positive control: it checks the Map/Set tag plus authoritative registry before clean_arr_ptr, so the same live receivers still report the correct sizes. The array collection tests reach their expected length assertions and fail only on the subsequent indexed read.
  5. js_dyn_index_get first confirms the receiver through the authoritative collection registry, then delegates to js_array_get_f64, which reproduces the same early rejection.

This also explains the three typed-feedback failures, since their get fallbacks converge on that dynamic/array accessor. Integer indexed reads are broken; the fractional-key undefined/no-mutation assertions remain the required control.

Required fix

Route authoritative Map/Set receivers before strict array cleaning in js_array_get_f64, matching the proven js_array_length ordering, or use an equivalent dedicated collection accessor.

Keep #8041's strict rejection unchanged for genuine non-array/non-collection receivers. The fix should retain:

  • receiver-tag gating before registry probes;
  • the authoritative Map/Set registry check (never trust the tag alone);
  • no collection registry probes for genuine arrays;
  • fractional Map/Set keys returning undefined without mutation;
  • all six exact tests passing locally.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regressionrustPull requests that update rust code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions