Skip to content

Array iteration protocol: for-of over a typed local bypasses a patched Array.prototype[Symbol.iterator]; restoring the original throws; getOwnPropertyDescriptor on a prototype symbol returns undefined #7760

Description

@proggeramlug

Three defects found while fixing #7542 (PR #7759), each independent of it and of each other. Filing together because they share a root area and the first two block writing a gap test for any of them.

1. for…of over a typed local ignores a replaced Array.prototype[Symbol.iterator]

const p: any = Array.prototype;
p[Symbol.iterator] = function* () { yield "patched"; };
const src = [1, 2, 3];
const out: any[] = []; for (const x of src) out.push(x);
console.log(out);        // node: ["patched"]   perry: [1,2,3]

const lit: any[] = []; for (const x of [1, 2, 3]) lit.push(x);
console.log(lit);        // node: ["patched"]   perry: ["patched"]  ✓

Over an array literal it is correct (that routes through js_get_iterator, which honours the flag). Over a typed local it is not, and the reason is that codegen emits a dense index loop with no runtime call at alljs_array_values_iter_obj and js_array_length appear only in the declare block of the emitted IR.

So this cannot be fixed where #7542 was: there is no runtime entry point to guard. It needs a codegen-side gate — e.g. the loop preheader loading a volatile global (the shape PERRY_ARRAY_INDEX_FAST_PATH_INVALIDATED already uses) and branching to the protocol path when the iteration protocol has been patched.

2. Restoring the original array iterator throws

const p: any = Array.prototype;
const orig = p[Symbol.iterator];
p[Symbol.iterator] = function* () { yield "x"; };
p[Symbol.iterator] = orig;                        // restore
for (const v of [1, 2, 3]) {}                     // TypeError: next is not a function

Reading Array.prototype[Symbol.iterator] returns a method bound to the prototype (js_object_get_symbol_property's array arm synthesizes it via js_class_method_bind), so storing it back and calling it with this === arr does not produce an array iterator.

Pre-existing and independent of #7542: it reproduces on for…of over an array literal, which routes through js_get_iterator and is untouched by that fix.

3. getOwnPropertyDescriptor on a prototype symbol returns undefined

Object.getOwnPropertyDescriptor(Array.prototype, Symbol.iterator)
// node: { value: [Function: values], writable: true, enumerable: false, configurable: true }
// perry: undefined

Which also removes the obvious workaround for (2).

Why these are worth having together

They make the whole family untestable in the gap suite today. Any test that patches Array.prototype[Symbol.iterator] takes the ORACLE down under run_parity_tests.sh — node constructs a SafeMap from an iterable and gets the patched value:

TypeError: Iterator value patched is not an entry object
    at new Map (<anonymous>)  node:internal/per_context/primordials:449

so the comparison runs against a crashed node. The natural fix is for the test to restore the original before returning — which (2) and (3) both block. Fixing either one makes a gap test for this family possible; until then #7759's coverage is a direct byte-for-byte comparison recorded in its description.

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