Summary
The generic array-like engine behind Array.prototype.<method>.call(...) reads elements on a path that bypasses array_spec_get, so a hole filled by the array's prototype is seen as a hole rather than as the inherited value.
const proto = { 1: "holeFill" };
const k = [0, , 2]; // hole at index 1
Object.setPrototypeOf(k, proto);
k[1] // "holeFill" (correct after #9219)
Array.prototype.join.call(k) // node: "0,holeFill,2" perry: "0,,2"
Array.prototype.indexOf.call(k, "holeFill") // node: 1 perry: -1
map and forEach diverge the same way — ES defines their hole check as HasProperty, which is a full prototype-chain walk, not an own-element test.
Not the setPrototypeOf bug
Pre-existing and independent of #9192. Found while fixing it; the control that establishes this is that the divergence reproduces identically when the prototype is itself an array — the long-supported shape. #9219 fixes direct indexed and named reads; these generic-engine element reads are a separate path it does not touch.
Impact
Silent wrong values from the most generic, most-copied array idiom in JS. Anything using Array.prototype.<m>.call on an array-like with an inherited fill — a common polyfill and arguments-handling shape — gets a hole where a value belongs.
Fix direction
Route the generic engine's element reads through array_spec_get (or the same ArrayCustomProto classification #9219 introduced in crates/perry-runtime/src/array/indexing.rs), and make the hole test a HasProperty walk rather than an own-element check when the receiver has a recorded prototype. Arrays with the default chain should keep the current fast read, gated on the existing per-array recorded-prototype check, so this costs nothing for the overwhelmingly common case.
Checks 106–110 of the differential harness in the #9219 branch already cover this.
Summary
The generic array-like engine behind
Array.prototype.<method>.call(...)reads elements on a path that bypassesarray_spec_get, so a hole filled by the array's prototype is seen as a hole rather than as the inherited value.mapandforEachdiverge the same way — ES defines their hole check asHasProperty, which is a full prototype-chain walk, not an own-element test.Not the setPrototypeOf bug
Pre-existing and independent of #9192. Found while fixing it; the control that establishes this is that the divergence reproduces identically when the prototype is itself an array — the long-supported shape. #9219 fixes direct indexed and named reads; these generic-engine element reads are a separate path it does not touch.
Impact
Silent wrong values from the most generic, most-copied array idiom in JS. Anything using
Array.prototype.<m>.callon an array-like with an inherited fill — a common polyfill andarguments-handling shape — gets a hole where a value belongs.Fix direction
Route the generic engine's element reads through
array_spec_get(or the sameArrayCustomProtoclassification #9219 introduced incrates/perry-runtime/src/array/indexing.rs), and make the hole test aHasPropertywalk rather than an own-element check when the receiver has a recorded prototype. Arrays with the default chain should keep the current fast read, gated on the existing per-array recorded-prototype check, so this costs nothing for the overwhelmingly common case.Checks 106–110 of the differential harness in the #9219 branch already cover this.