Skip to content

Array.prototype.{join,indexOf,map,forEach}.call(arr) does not fill holes through a custom prototype #9221

Description

@proggeramlug

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.

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