Skip to content

fix(array): ask the typed-array question before clean_arr_ptr rejects it - #8090

Merged
proggeramlug merged 3 commits into
PerryTS:mainfrom
jdalton:fix/typed-array-receiver-funnel
Aug 14, 2026
Merged

fix(array): ask the typed-array question before clean_arr_ptr rejects it#8090
proggeramlug merged 3 commits into
PerryTS:mainfrom
jdalton:fix/typed-array-receiver-funnel

Conversation

@jdalton

@jdalton jdalton commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

On a typed array, fill, reverse and copyWithin silently do nothing. No error, no diagnostic — the array is simply unchanged. sort, with and toReversed are broken by the same cause and are not fixed here (see the end).

const f = new Uint16Array(4); f[0]=1;f[1]=2;f[2]=3;f[3]=4
f.fill(9,0,2)
const r = new Uint16Array(4); r[0]=1;r[1]=2;r[2]=3;r[3]=4
r.reverse()
const c = new Uint16Array(4); c[0]=1;c[1]=2;c[2]=3;c[3]=4
c.copyWithin(0,2)
// perry: 1,2,3,4  /  1,2,3,4  /  1,2,3,4     (nothing happened)
// node:  9,9,3,4  /  4,3,2,1  /  3,4,3,4

Root cause: the typed-array question was asked too late

Each mutator already had a typed-array delegation from #3148. All of them sat below the shared receiver funnel, which had already rejected the receiver.

array/header.rs, clean_arr_ptr:

// A declared TypeScript type is a hint, never a layout fact. Reject
// every tracked non-array at this shared funnel before treating its
// payload as an ArrayHeader (#7574).
if obj_type != crate::gc::GC_TYPE_ARRAY {
    return std::ptr::null();
}

Since the 2026-07-09 audit, typed_array_alloc allocates every typed array through arena_alloc_gc_old(..., GC_TYPE_TYPED_ARRAY) — a real tracked GcHeader. Typed arrays used to be header-less side-table allocations, which the funnel's is_registered_buffer || lookup_typed_array_kind arm let through.

So clean_arr_ptr_mut returns null for a typed array, every mutator returns at its arr.is_null() early-out, and the #3148 delegation below it is unreachable. The pointer was never mis-boxed and the registry was never stale — lookup_typed_array_kind answers correctly, it just never got asked.

clean_arr_ptr_still_rejects_a_typed_array_receiver pins both halves of that (registered = yes, cleaned = null) so the diagnosis is asserted rather than inferred.

The fix

One new funnel, typed_array_receiver in array/header.rs, consulted before the clean by js_array_fill, js_array_fill_range, js_array_reverse and js_array_copy_within.

clean_arr_ptr's #7574 rejection is deliberately untouched. It is correct — a TypedArrayHeader's per-kind storage really is not boxed-f64 slots. The bug was the ordering, not the routing.

Why the runtime and not codegen

Codegen could decline is_array_expr for a statically-typed typed array, but that strands the ~40 other helpers in array/ implementing the same #3148 contract (sort, index get/set, forEach/map/filter/join…), which are the only path those receivers have.

It also would not fix the any receiver at all. HIR's copyWithin fold declines only for a known typed array, so an unknown-typed receiver lowers straight to js_array_copy_within — the same helper. One ordering fix covers both; the codegen route needs two.

js_array_copy_within also gained the Buffer/Uint8Array arm, delegating to buffer_dispatch's byte-granularity copyWithin. That shape arrives through the same fold and was the only Uint8Array failure.

Test plan

330 assertions: fill/reverse/copyWithin × Uint8Array/Uint16Array/Int32Array/Float64Array/plain Array × three receiver typings × function and module scope, plus 0/1/2/3-arg fill, odd-length reverse, and negative and out-of-range copyWithin. Byte-compared against node 26.5.1.

program lines wrong before wrong after
Uint16Array 66 52 0
Int32Array 66 52 0
Float64Array 66 52 0
Uint8Array 66 8 0
plain Array 66 0 0
total 330 164 0

Both arms use the same compiler against different runtimes — the "before" arm links a snapshot of the pre-fix libperry_{runtime,stdlib}.a via PERRY_RUNTIME_DIR, per CLAUDE.md's warning that cargo build -p perry-runtime alone does not emit those archives and leaves an A/B comparing a stale one.

Verified by sabotage. Making typed_array_receiver return None — the pre-fix state — turns 5 of 8 tests red, including the element-width test (left: [0.0, 0.0], right: [-2147483648.0, -2147483648.0]). The 3 that stay green are precisely the ones that should: the clean_arr_ptr precondition and the plain-Array controls.

RUST_TEST_THREADS=1 cargo test --release -p perry-runtime2338 passed, 0 failed. (That flag is required; the runtime's tests share process-global side tables.)

Why the element-shape matrix did not catch this, and why it should not be extended to

element_shape_matrix_tests.rs names copyWithin, but every fixture is shaped(4) — a plain ArrayHeader built by js_array_push_f64 — and the property it asserts is element-shape proof revocation, which a typed array can never carry. It is a correct test of a different invariant.

The real gap was that nothing drove an Array.prototype FFI entry point with a typed-array receiver. array/typed_array_receiver_tests.rs is now that test.

Related issue

Refs #2879.

Deliberately not fixed here

Same root cause, still broken, each needing its own oracle matrix (comparator forms, and new-array returns rather than in-place mutation): the post-clean delegations at array/sort.rs:563/:659 and array/immutable.rs:37/:61/:106/:246. Measured on a statically-typed Int32Array: sort() leaves 3,1,4,2 unsorted, and with(1,42) and toReversed() both return an empty array. Named in the changelog fragment so they are not lost.

Pre-existing red, not from this PR: crates/perry-codegen/tests/loop_safepoint_purity.rs fails 2 of 8, reproduced identically with perry-runtime and perry-hir checked out at the base commit; check_file_size.sh is red on timer.rs at 2010 lines; the addr-class ratchet reports a stale baseline for header.rs and dyn_index.rs, which I checked with the script's own scan_text — base and current both have 3 handle-floor sites, so that staleness predates this change and the audit exits 0.

No version bump, no CLAUDE.md edit, no CHANGELOG.md edit, per the template.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed typed-array fill, ranged fill, reverse, and copyWithin operations that previously performed no action.
    • Preserved correct index ranges, omitted arguments, buffer handling, and typed-element conversions.
    • Ensured regular array behavior remains unchanged.
  • Tests

    • Added regression coverage for typed-array mutators, including Buffer and Uint8Array scenarios.

Loading
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.

2 participants