Skip to content

An array's custom [[Prototype]] chain is truncated after the first hop (depth, null termination, strict [[Set]]) #9785

Description

@proggeramlug
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.

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions