Skip to content

spread: a replaced Array.prototype[Symbol.iterator] is ignored by [...arr] #7542

Description

@proggeramlug

Replacing Array.prototype[Symbol.iterator] changes what [...arr] yields in node. In Perry the spread ignores the replacement and returns the builtin element copy.

const arrProto: any = Array.prototype;
const original = arrProto[Symbol.iterator];
arrProto[Symbol.iterator] = function* () { yield "patched"; };

console.log([...[1, 2, 3]]);   // node: [ 'patched' ]   perry: [ 1, 2, 3 ]

arrProto[Symbol.iterator] = original;
console.log([...[1, 2, 3]]);   // node: [ 1, 2, 3 ]     perry: [ 1, 2, 3 ]

An own [Symbol.iterator] on the instance is honoured correctly, so this is specific to the prototype-level replacement:

const a: any = [1, 2, 3];
a[Symbol.iterator] = function* () { yield "own"; };
console.log([...a]);           // node: [ 'own' ]       perry: [ 'own' ]  ✓

Where

The machinery already exists and is not consulted on this path. array/indexing.rs keeps a sticky flag:

static ARRAY_PROTO_ITERATOR_MODIFIED: AtomicBool = AtomicBool::new(false);
pub(crate) fn note_array_proto_iterator_write(obj: usize, sym_key: usize) {}
pub(crate) fn array_proto_iterator_modified() -> bool {}

js_get_iterator (symbol/iterator.rs) honours it — when the flag is set it reads the patched method off Array.prototype and calls it with this === val. array_from_spread_value never asks. Its generic symbol lookup reads own symbol props only (hence the own-@@iterator case working), so for a plain array it misses and falls to the js_array_is_array arm, which drives the builtin array_values_iter unconditionally.

Two things to check when fixing:

  1. Whether note_array_proto_iterator_write actually fires for Array.prototype[Symbol.iterator] = fn — if the flag never flips, both this path and js_get_iterator's are dead.
  2. %ArrayIteratorPrototype%.next replacement is a separate, currently-unmodelled case: builtin array iterators dispatch .next() through the class-id tower, so a patched prototype next is bypassed there too.

Provenance

Found while building a 32-case [...arr] semantics matrix for #7533. Pre-existing: reproduces byte-identically at f06270d06 and is unchanged by #7540's dense fast path, which gates on array_proto_iterator_modified() and so cannot make this worse — with the flag set it declines and falls through to the same (already wrong) protocol path.

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