Found while fixing #8100 (PR #8109), which is the same disease in the
js_typed_array_* element helpers. This one is in the Uint8Array-specialized
pair and is not fixed by that PR — measured pre-fix and post-fix on the
same probe, byte-identical output, so #8109 neither causes nor fixes it.
Reproducer
let Q: Uint8Array = new Uint8Array(2);
Q = [9, 10] as any;
Q[0] = 5;
console.log("store:", JSON.stringify(Q));
let k: any = 1;
console.log("dyn:", Q[k]);
console.log("read:", Q[0], Q[1], Q.length, Q.at(1));
let R: Uint8Array = new Uint8Array([3, 4]);
console.log("real u8:", R[0], R[1], R.length);
const B = Buffer.from([1, 2]);
console.log("buf:", B[0], B[1]);
node 26.5.1 perry
store: [5,10] store: [9,10] <- the write is SILENTLY DROPPED
dyn: 10 dyn: undefined
read: 5 10 2 10 read: undefined undefined 2 10
real u8: 3 4 2 real u8: 3 4 2 (control, OK)
buf: 1 2 buf: 1 2 (control, OK)
Both sides exit 0. Q.length and Q.at(1) are already correct, so the binding
really does hold the plain array — only the specialized element accessors are
wrong.
Reproduced at b5ab0262e (#8109's branch) and at its parent, --profile perry-dev, --no-auto-optimize --no-cache, PERRY_RUNTIME_DIR pinned to a
freshly built archive pair.
Root cause
crates/perry-runtime/src/typedarray/access.rs:
// :605
pub extern "C" fn js_uint8array_index_get_value(target: *const TypedArrayHeader, index: i32) -> f64 {
...
if let Some(kind) = lookup_typed_array_kind(addr) {
if !matches!(kind, KIND_UINT8 | KIND_UINT8_CLAMPED) { return undefined; }
js_typed_array_get(addr as *const TypedArrayHeader, index)
} else if crate::buffer::is_registered_buffer(addr) {
crate::buffer::js_buffer_index_get_value(...)
} else {
undefined // <- a plain array / object lands here
}
}
// :635 js_uint8array_set — same three-way shape, same trailing arm, silent no-op
A receiver that is neither a registered typed array nor a registered buffer
falls off the end. Codegen reaches these helpers through
is_uint8array_receiver (perry-codegen/src/expr/index_get.rs /
index_set.rs), which is a different predicate from the local_type_hint
one #8100 is about — it keys on receiver_class_name — but it still fires for
a reassigned Uint8Array local.
Note the asymmetry with #8100: these helpers are memory-SAFE (they validate
before dereferencing), they just answer undefined / drop the store instead of
falling back to the ordinary [[Get]] / [[Set]].
Suggested fix
The get side is the same shape as #8109's: replace the trailing undefined
with a dispatch through crate::typedarray::classify_element_read_receiver
(added in #8109) → js_dyn_index_get. The wrong-KIND arm above must stay as it
is — the classifier answers TypedArray for a registered typed array of any
kind, so only the final else changes.
The set side needs its own decision and is why this is a separate issue rather
than part of #8109, which is scoped to reads: a dropped store is not the same
call as a wrong read, js_dyn_index_set has its own return-value contract, and
js_uint8array_set takes an i32 value rather than a NaN-boxed f64.
Found while fixing #8100 (PR #8109), which is the same disease in the
js_typed_array_*element helpers. This one is in theUint8Array-specializedpair and is not fixed by that PR — measured pre-fix and post-fix on the
same probe, byte-identical output, so #8109 neither causes nor fixes it.
Reproducer
Both sides exit 0.
Q.lengthandQ.at(1)are already correct, so the bindingreally does hold the plain array — only the specialized element accessors are
wrong.
Reproduced at
b5ab0262e(#8109's branch) and at its parent,--profile perry-dev,--no-auto-optimize --no-cache,PERRY_RUNTIME_DIRpinned to afreshly built archive pair.
Root cause
crates/perry-runtime/src/typedarray/access.rs:A receiver that is neither a registered typed array nor a registered buffer
falls off the end. Codegen reaches these helpers through
is_uint8array_receiver(perry-codegen/src/expr/index_get.rs/index_set.rs), which is a different predicate from thelocal_type_hintone #8100 is about — it keys on
receiver_class_name— but it still fires fora reassigned
Uint8Arraylocal.Note the asymmetry with #8100: these helpers are memory-SAFE (they validate
before dereferencing), they just answer
undefined/ drop the store instead offalling back to the ordinary
[[Get]]/[[Set]].Suggested fix
The get side is the same shape as #8109's: replace the trailing
undefinedwith a dispatch through
crate::typedarray::classify_element_read_receiver(added in #8109) →
js_dyn_index_get. The wrong-KIND arm above must stay as itis — the classifier answers
TypedArrayfor a registered typed array of anykind, so only the final
elsechanges.The set side needs its own decision and is why this is a separate issue rather
than part of #8109, which is scoped to reads: a dropped store is not the same
call as a wrong read,
js_dyn_index_sethas its own return-value contract, andjs_uint8array_settakes ani32value rather than a NaN-boxedf64.