const mid: any = { 5: "from-mid" };
const protoArr: any = []; protoArr[3] = "from-protoArr";
Object.setPrototypeOf(protoArr, mid);
const arr: any = []; Object.setPrototypeOf(arr, protoArr);
console.log(arr[3], arr[5], 5 in arr);
// node : from-protoArr from-mid true
// perry: from-protoArr undefined false <-- `mid` is never consulted
Three sub-defects, all reproducing on main@12efed1222:
1. Depth. When the custom prototype is itself an array and the index misses on it, the walk falls through to the default Array.prototype -> Object.prototype tail instead of continuing up that array's own [[Prototype]].
2. Termination. A chain deliberately cut with Object.setPrototypeOf(protoArr, null) still sees the default Array.prototype / Object.prototype indices, so properties appear that the spec says are gone:
cut.arrayProtoLeak node "undefined" perry "default-array-proto"
cut.objectProtoLeak node "undefined" perry "default-object-proto"
cut.has42 node false perry true
3. Strict [[Set]]. Assigning over an inherited non-writable index in strict mode creates an own element instead of throwing:
strictSet.threw node "TypeError" perry "no-throw"
strictSet.value node "readonly" perry "written"
strictSet.own node false perry true
Source
crates/perry-runtime/src/array/indexing_proto_chain.rs:
array_spec_get, array_spec_has_index, array_oob_prototype_get: the ArrayCustomProto::Array(_) arm checks only that prototype's own indices, then falls through to the default tail. The in-source comment states the approximation and pins it to one test262 copyWithin case, so the trade-off was deliberate — but it is wrong for any chain deeper than one hop, and for a null-terminated one.
array_object_proto_index_owner returns 0 ("no owner") for a Proxy and for an invalid pointer, which is what lets strict [[Set]] create the own element.
Both were closed on 2026-09-02 in a batch. #9370 (reapplying #9297) genuinely improved the single-hop case, and #9249's defineProperty work is genuinely fixed — leave that closed. The three behaviours above are byte-identical to 2026-09-01 and were never covered.
Done when
secret-tests/cases/adversarial/recent/40_array_proto_chain_depth.ts matches node. It covers all three, plus controls proving a plain array still inherits normally.
Three sub-defects, all reproducing on
main@12efed1222:1. Depth. When the custom prototype is itself an array and the index misses on it, the walk falls through to the default
Array.prototype->Object.prototypetail instead of continuing up that array's own[[Prototype]].2. Termination. A chain deliberately cut with
Object.setPrototypeOf(protoArr, null)still sees the defaultArray.prototype/Object.prototypeindices, so properties appear that the spec says are gone:3. Strict
[[Set]]. Assigning over an inherited non-writable index in strict mode creates an own element instead of throwing:Source
crates/perry-runtime/src/array/indexing_proto_chain.rs:array_spec_get,array_spec_has_index,array_oob_prototype_get: theArrayCustomProto::Array(_)arm checks only that prototype's own indices, then falls through to the default tail. The in-source comment states the approximation and pins it to one test262copyWithincase, so the trade-off was deliberate — but it is wrong for any chain deeper than one hop, and for anull-terminated one.array_object_proto_index_ownerreturns0("no owner") for a Proxy and for an invalid pointer, which is what lets strict[[Set]]create the own element.Why this is not #9220 / #9221
Both were closed on 2026-09-02 in a batch. #9370 (reapplying #9297) genuinely improved the single-hop case, and #9249's
definePropertywork is genuinely fixed — leave that closed. The three behaviours above are byte-identical to 2026-09-01 and were never covered.Done when
secret-tests/cases/adversarial/recent/40_array_proto_chain_depth.tsmatches node. It covers all three, plus controls proving a plain array still inherits normally.