Skip to content

Iterator helper fixes - #1622

Merged
saghul merged 3 commits into
masterfrom
iterator-helper-fixes
Jul 31, 2026
Merged

Iterator helper fixes#1622
saghul merged 3 commits into
masterfrom
iterator-helper-fixes

Conversation

@saghul

@saghul saghul commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
  • Fix the throw paths of the iterator helpers
  • Close the iterator properly in Iterator.concat().return()
  • Make Iterator.concat() return an Iterator Helper

saghul added 3 commits July 31, 2026 13:01
Two deviations, both verified against V8 and JavaScriptCore, which agree with
each other and with the spec:

- An abrupt completion did not finish the helper, because the shared |fail|
  label left it->done at *pdone, which is false when the throw happened after a
  successful step. The next next() therefore stepped the underlying iterator
  again and threw again, where the spec sets the generator's [[GeneratorState]]
  to completed and reports {value: undefined, done: true}. Affected map,
  filter, flatMap, take, drop and Iterator.concat.

- A throw from stepping the iterator closed it. GetIteratorDirect and
  IteratorStepValue propagate as is; the latter only marks the iterator record
  done. Only a throw from the callback goes through IfAbruptCloseIterator. So
  the iterator's return method was called when its next method threw, when the
  done or value getter of the result threw, when the result was not an object,
  and when reading .next threw. Affected all of the helpers as well as every,
  some, find, forEach and reduce; reduce also closed the iterator when it threw
  on an empty one, and flatMap closed the inner iterator when stepping it
  threw. toArray was already correct.

test262 does not cover either: the tests stop at the first throwing next() and
only check the type of the exception, and the error from the extra return
lookup is swallowed because the iterator is closed with an exception pending.
js_iterator_concat_return() called the .return method of the iterator it was
iterating by hand, which was wrong in three ways, all of them verified against
V8 and JavaScriptCore:

- It did not check whether the method is there, so closing a concatenation of
  plain arrays threw: Iterator.concat([1,2]) yields an array iterator, which has
  no .return method, and calling it raised "TypeError: not a function".
- It did not check that the result of the call is an object.
- It returned that result as is, so .return() evaluated to whatever the
  underlying iterator handed back, or to undefined when there was nothing to
  close, instead of to an iterator result object.

JS_IteratorClose() does all of that, so use it, and return
CreateIterResultObject(undefined, true) as %IteratorHelperPrototype%.return
does. The iterator is now finished even when closing it throws, and .return is
declared with a length of 0 to match the method it stands in for.

test262 misses this: its only close test uses an iterator whose .return method
exists and returns an object, and it never inspects the result of .return().
The spec creates the iterator with CreateIteratorFromClosure(closure,
"Iterator Helper", %IteratorHelperPrototype%), so its prototype, its next and
return methods and its toStringTag are the ones every other iterator helper
gets. quickjs gave it a prototype of its own with a private copy of next and
return and an "Iterator Concat" tag, which V8 and JavaScriptCore both disagree
with:

  const it = Iterator.concat([1]);
  Object.getPrototypeOf(it) === Object.getPrototypeOf([].values().map(x => x));
  Object.prototype.toString.call(it);  // "[object Iterator Helper]"
  it.next.length;                      // 0

Point the class at %IteratorHelperPrototype% and dispatch on the class id in
js_iterator_helper_next(), which now serves both kinds of helper: the ones with
a JSIteratorHelperData and the concatenation with its JSIteratorConcatData. The
concat state, the reentrancy guard and the closing behaviour are unchanged.

test262 does not notice: concat/proto.js checks the prototype of the
Iterator.concat function, not the prototype of what it returns.
@saghul
saghul merged commit 5e50c0c into master Jul 31, 2026
128 checks passed
@saghul
saghul deleted the iterator-helper-fixes branch July 31, 2026 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant