Summary
An indexed write to an array never consults the prototype chain. arr[i] = v lowers to js_array_set_f64_extend_strict, whose fast lanes go straight to the element storage; only array_spec_set (used by push/shift/reverse) does the [[Set]] walk.
const proto = {};
Object.defineProperty(proto, 9, { set(v) { calls.push(v); }, get() { return "acc9"; } });
const a = [1, 2, 3];
Object.setPrototypeOf(a, proto);
a[9] = 5;
// node: the setter runs; a has NO own property "9"
// perry: no setter call; an own element 9 is created
The same gap applies to a non-writable inherited index, where ES requires the write to be rejected (silently in sloppy mode, TypeError in strict).
Not the setPrototypeOf bug
This is pre-existing and independent of #9192. It was found while fixing that issue, and the control that proves it is the point: the divergence reproduces identically when the prototype is itself an array, i.e. on the shape that has always been supported. #9192's fix (#9219) corrects the read side; this is the write side and is untouched by it.
Impact
Silent, and in the direction that matters: a program that installs an index setter to intercept writes sees nothing, and the array quietly gains an own property the author intended to be virtual. Accessor-backed array-likes and the ES5 Object.create(Array.prototype) idiom both hit it.
Fix direction
js_array_set_f64_extend_strict's fast lanes need the same recorded-prototype classification the read path now has — ArrayCustomProto::{Null, Array, Other} in crates/perry-runtime/src/array/indexing.rs, added by #9219. The cheap gate is the existing per-array "has a recorded prototype" check: an array with the default chain keeps today's lanes untouched, and only a retargeted array pays the [[Set]] walk. Note the ordering requirement — an inherited data property does not stop an own-property create, only an accessor or a non-writable one does.
There is a differential harness with controls for this in the #9219 branch (checks 085–087), so the regression instrument already exists.
Summary
An indexed write to an array never consults the prototype chain.
arr[i] = vlowers tojs_array_set_f64_extend_strict, whose fast lanes go straight to the element storage; onlyarray_spec_set(used bypush/shift/reverse) does the[[Set]]walk.The same gap applies to a non-writable inherited index, where ES requires the write to be rejected (silently in sloppy mode,
TypeErrorin strict).Not the setPrototypeOf bug
This is pre-existing and independent of #9192. It was found while fixing that issue, and the control that proves it is the point: the divergence reproduces identically when the prototype is itself an array, i.e. on the shape that has always been supported. #9192's fix (#9219) corrects the read side; this is the write side and is untouched by it.
Impact
Silent, and in the direction that matters: a program that installs an index setter to intercept writes sees nothing, and the array quietly gains an own property the author intended to be virtual. Accessor-backed array-likes and the ES5
Object.create(Array.prototype)idiom both hit it.Fix direction
js_array_set_f64_extend_strict's fast lanes need the same recorded-prototype classification the read path now has —ArrayCustomProto::{Null, Array, Other}incrates/perry-runtime/src/array/indexing.rs, added by #9219. The cheap gate is the existing per-array "has a recorded prototype" check: an array with the default chain keeps today's lanes untouched, and only a retargeted array pays the[[Set]]walk. Note the ordering requirement — an inherited data property does not stop an own-property create, only an accessor or a non-writable one does.There is a differential harness with controls for this in the #9219 branch (checks 085–087), so the regression instrument already exists.