Problem
The synchronous iterator-helper implementation captured next values, but two boundaries discarded or reinterpreted the capture:
iterStepM(iterator, next_method)only called the captured value when it was already callable. A captured non-callable value fell back tocallMethod(iterator, "next", ...), which performed a second observable lookup and could call a replacement method instead of throwing for the original[[NextMethod]].getIteratorFlattenableObjeagerly readiterator.nextbut returned only the iterator object.flatMap,zip, andzipKeyedtherefore lost theGetIteratorDirectrecord;flatMapimmediately readnextagain, while joint-iteration helpers stepped through dynamicnextlookups.
This violated the iterator-helpers proposal's GetIteratorDirect / GetIteratorFlattenable contracts and created an attacker-controlled re-entrancy surface through accessors and proxies.
Required implementation
- Represent direct/flattenable acquisition as an iterator record containing both
[[Iterator]]and the exact captured[[NextMethod]]. - Make stepping with a captured method call that exact value unconditionally; a non-callable captured value throws
TypeErrorwithout consultingiterator.nextagain. - Preserve the record through lazy
flatMap,Iterator.zip,Iterator.zipKeyed, and longest-mode padding. - Keep genuinely dynamic internal cursors separate and explicit rather than encoding "no captured method" as JavaScript
undefined. - Add adversarial tests for getter/proxy lookup counts, replacement-after-first-read, non-callable captures, abrupt completion, and close ordering.
- Run focused unit coverage and the relevant Test262 Iterator helper subtrees with before/after counts.
Constraints
- No duck-typing fallback and no repeated property lookup after capture.
- No workaround, proposal-test exclusion, or special case for built-in iterators.
- Do not change Map/Set iterator implementation or Promise/microtask/Worker lifecycle as part of this issue.