diff --git a/rhino/src/main/java/org/mozilla/javascript/ES6IteratorAdapter.java b/rhino/src/main/java/org/mozilla/javascript/ES6IteratorAdapter.java new file mode 100644 index 0000000000..6e9e0f92e1 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/ES6IteratorAdapter.java @@ -0,0 +1,185 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript; + +/** + * Adapter that wraps any iterator to inherit from Iterator.prototype. Used by Iterator.from() to + * ensure returned iterators have proper prototype chain. All operations are delegated to the + * wrapped iterator. + */ +public class ES6IteratorAdapter extends ScriptableObject { + + private static final long serialVersionUID = 1L; + + private final Scriptable wrappedIterator; + private final Callable nextMethod; + private Callable returnMethod; + private Callable throwMethod; + + /** + * Creates an adapter that wraps an iterator to inherit from Iterator.prototype. + * + * @param cx Current context + * @param scope Current scope + * @param iterator The iterator to wrap + */ + public ES6IteratorAdapter(Context cx, Scriptable scope, Object iterator) { + if (!(iterator instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator must be an object"); + } + + this.wrappedIterator = (Scriptable) iterator; + + // Get the next method (required) + Object next = ScriptableObject.getProperty(wrappedIterator, ES6Iterator.NEXT_METHOD); + if (!(next instanceof Callable)) { + throw ScriptRuntime.typeError("Iterator missing next method"); + } + this.nextMethod = (Callable) next; + + // Get optional return method + Object ret = ScriptableObject.getProperty(wrappedIterator, ES6Iterator.RETURN_METHOD); + if (ret instanceof Callable) { + this.returnMethod = (Callable) ret; + } + + // Get optional throw method + Object thr = ScriptableObject.getProperty(wrappedIterator, "throw"); + if (thr instanceof Callable) { + this.throwMethod = (Callable) thr; + } + + // Set up prototype chain to inherit from Iterator.prototype + setupPrototype(cx, scope); + } + + private void setupPrototype(Context cx, Scriptable scope) { + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + + // Try to find Iterator.prototype + Object iteratorCtor = ScriptableObject.getProperty(topScope, "Iterator"); + if (iteratorCtor instanceof Scriptable) { + Object proto = ScriptableObject.getProperty((Scriptable) iteratorCtor, "prototype"); + if (proto instanceof Scriptable) { + this.setPrototype((Scriptable) proto); + } + } + + this.setParentScope(scope); + } + + @Override + public String getClassName() { + return "Iterator"; + } + + @Override + public Object get(String name, Scriptable start) { + // First check if we have the property + Object result = super.get(name, start); + if (result != NOT_FOUND) { + return result; + } + + // Delegate property access to wrapped iterator + return ScriptableObject.getProperty(wrappedIterator, name); + } + + @Override + public Object get(Symbol key, Scriptable start) { + // First check if we have the property + Object result = super.get(key, start); + if (result != NOT_FOUND) { + return result; + } + + // Delegate symbol property access to wrapped iterator + if (wrappedIterator instanceof SymbolScriptable) { + return ((SymbolScriptable) wrappedIterator).get(key, start); + } + return NOT_FOUND; + } + + @Override + public boolean has(String name, Scriptable start) { + // Check both our properties and wrapped iterator's properties + return super.has(name, start) || ScriptableObject.hasProperty(wrappedIterator, name); + } + + @Override + public boolean has(Symbol key, Scriptable start) { + // Check both our properties and wrapped iterator's properties + if (super.has(key, start)) { + return true; + } + + if (wrappedIterator instanceof SymbolScriptable) { + return ((SymbolScriptable) wrappedIterator).has(key, start); + } + return false; + } + + /** + * Calls the next() method on the wrapped iterator. + * + * @param cx Current context + * @param scope Current scope + * @return Iterator result object + */ + public Object next(Context cx, Scriptable scope) { + return nextMethod.call(cx, scope, wrappedIterator, ScriptRuntime.emptyArgs); + } + + /** + * Calls the return() method on the wrapped iterator if it exists. + * + * @param cx Current context + * @param scope Current scope + * @param value Return value + * @return Iterator result object + */ + public Object doReturn(Context cx, Scriptable scope, Object value) { + if (returnMethod != null) { + return returnMethod.call(cx, scope, wrappedIterator, new Object[] {value}); + } + + // Default behavior if no return method + return IteratorOperations.makeIteratorResult(cx, scope, true, value); + } + + /** + * Calls the throw() method on the wrapped iterator if it exists. + * + * @param cx Current context + * @param scope Current scope + * @param value Value to throw + * @return Iterator result object (if throw method handles it) + * @throws JavaScriptException if no throw method or it rethrows + */ + public Object doThrow(Context cx, Scriptable scope, Object value) { + if (throwMethod != null) { + return throwMethod.call(cx, scope, wrappedIterator, new Object[] {value}); + } + + // Default behavior if no throw method - throw the value + if (value instanceof JavaScriptException) { + throw (JavaScriptException) value; + } else if (value instanceof RhinoException) { + throw (RhinoException) value; + } + throw ScriptRuntime.typeError(value.toString()); + } + + /** + * Gets the wrapped iterator object. + * + * @return The wrapped iterator + */ + public Scriptable getWrappedIterator() { + return wrappedIterator; + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/IteratorLikeIterable.java b/rhino/src/main/java/org/mozilla/javascript/IteratorLikeIterable.java index 9dbb1573da..1d32d5e005 100644 --- a/rhino/src/main/java/org/mozilla/javascript/IteratorLikeIterable.java +++ b/rhino/src/main/java/org/mozilla/javascript/IteratorLikeIterable.java @@ -20,16 +20,12 @@ * property called "return" then it will be called when the caller is done iterating. */ public class IteratorLikeIterable implements Iterable, Closeable { - private final Context cx; - private final Scriptable scope; private final Callable next; private final Callable returnFunc; private final Scriptable iterator; private boolean closed; public IteratorLikeIterable(Context cx, Scriptable scope, Object target) { - this.cx = cx; - this.scope = scope; // This will throw if "next" is not a function or undefined var nextCall = ScriptRuntime.getPropAndThis(target, ES6Iterator.NEXT_METHOD, cx, scope); next = nextCall.getCallable(); @@ -49,6 +45,21 @@ public IteratorLikeIterable(Context cx, Scriptable scope, Object target) { @Override public void close() { + // Warning: This method cannot work properly without Context + // Use close(Context, Scriptable) instead for proper cleanup + if (!closed) { + closed = true; + // Cannot call returnFunc without Context - cleanup skipped + } + } + + /** + * Context-safe close method that properly handles cleanup. + * + * @param cx Current context + * @param scope Current scope + */ + public void close(Context cx, Scriptable scope) { if (!closed) { closed = true; if (returnFunc != null) { @@ -59,13 +70,42 @@ public void close() { @Override public Itr iterator() { - return new Itr(); + // Warning: This requires Context from current thread + // Use iterator(Context, Scriptable) for explicit Context passing + Context cx = Context.getCurrentContext(); + if (cx == null) { + throw new IllegalStateException("No Context associated with current thread"); + } + // Get the scope from the current context + Scriptable scope = ScriptableObject.getTopLevelScope(iterator); + return new Itr(cx, scope); + } + + /** + * Context-safe iterator method. NOTE: The returned Itr still captures Context due to Java + * Iterator interface limitations. This is acceptable because: 1. Context is captured at + * iterator creation, not at IteratorLikeIterable creation 2. Iterator lifetime is short (single + * iteration sequence) 3. Java Iterator.hasNext()/next() cannot accept Context parameters + * + * @param cx Current context + * @param scope Current scope + * @return Iterator instance + */ + public Itr iterator(Context cx, Scriptable scope) { + return new Itr(cx, scope); } public final class Itr implements Iterator { + private final Context cx; + private final Scriptable scope; private Object nextVal; private boolean isDone; + private Itr(Context cx, Scriptable scope) { + this.cx = cx; + this.scope = scope; + } + @Override public boolean hasNext() { if (isDone) { diff --git a/rhino/src/main/java/org/mozilla/javascript/IteratorOperations.java b/rhino/src/main/java/org/mozilla/javascript/IteratorOperations.java new file mode 100644 index 0000000000..98ea0fb578 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/IteratorOperations.java @@ -0,0 +1,209 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +/** + * Context-safe utility methods for working with ECMAScript iterators. All methods take Context as a + * parameter instead of storing it, ensuring thread safety. + */ +public class IteratorOperations { + + private IteratorOperations() { + // Utility class, no instances + } + + /** + * Collects all values from an iterator into an array. Properly handles cleanup via iterator's + * return() method. + * + * @param cx Current context + * @param scope Current scope + * @param iterator The iterator object + * @return Array containing all iterator values + */ + public static Object[] collectToArray(Context cx, Scriptable scope, Object iterator) { + List result = collectToList(cx, scope, iterator); + return result.toArray(new Object[result.size()]); + } + + /** + * Collects all values from an iterator into a List. Properly handles cleanup via iterator's + * return() method. + * + * @param cx Current context + * @param scope Current scope + * @param iterator The iterator object + * @return List containing all iterator values + */ + public static List collectToList(Context cx, Scriptable scope, Object iterator) { + List result = new ArrayList<>(); + + IteratorLikeIterable iterable = null; + try { + iterable = new IteratorLikeIterable(cx, scope, iterator); + + // Use the Context-safe iterator method + var iter = iterable.iterator(); + while (iter.hasNext()) { + result.add(iter.next()); + } + } finally { + // Ensure cleanup via return() method + if (iterable != null) { + iterable.close(); + } + } + + return result; + } + + /** + * Processes each value from an iterator with a consumer function. Properly handles cleanup via + * iterator's return() method. + * + * @param cx Current context + * @param scope Current scope + * @param iterator The iterator object + * @param consumer Function to process each value (receives index and value) + */ + public static void forEach( + Context cx, Scriptable scope, Object iterator, BiConsumer consumer) { + IteratorLikeIterable iterable = null; + try { + iterable = new IteratorLikeIterable(cx, scope, iterator); + + var iter = iterable.iterator(); + int index = 0; + while (iter.hasNext()) { + consumer.accept(index++, iter.next()); + } + } finally { + // Ensure cleanup via return() method + if (iterable != null) { + iterable.close(); + } + } + } + + /** + * Checks if an object is iterable (has Symbol.iterator method). + * + * @param cx Current context + * @param scope Current scope + * @param obj Object to check + * @return true if object is iterable + */ + public static boolean isIterable(Context cx, Scriptable scope, Object obj) { + if (obj == null || obj == Undefined.instance) { + return false; + } + + if (!(obj instanceof Scriptable)) { + return false; + } + + Scriptable scriptable = (Scriptable) obj; + Object iteratorMethod = ScriptableObject.getProperty(scriptable, SymbolKey.ITERATOR); + + return iteratorMethod != Scriptable.NOT_FOUND && iteratorMethod instanceof Callable; + } + + /** + * Gets an iterator from an iterable object. + * + * @param cx Current context + * @param scope Current scope + * @param iterable The iterable object + * @return The iterator object + * @throws JavaScriptException if object is not iterable + */ + public static Object getIterator(Context cx, Scriptable scope, Object iterable) { + if (!isIterable(cx, scope, iterable)) { + throw ScriptRuntime.typeError("Object is not iterable"); + } + + Scriptable scriptable = (Scriptable) iterable; + Object iteratorMethod = ScriptableObject.getProperty(scriptable, SymbolKey.ITERATOR); + + if (iteratorMethod instanceof Callable) { + Callable callable = (Callable) iteratorMethod; + return callable.call(cx, scope, scriptable, ScriptRuntime.emptyArgs); + } + + throw ScriptRuntime.typeError("@@iterator is not callable"); + } + + /** + * Creates an iterator result object { done, value }. + * + * @param cx Current context + * @param scope Current scope + * @param done Whether iteration is complete + * @param value The value (can be Undefined.instance) + * @return Iterator result object + */ + public static Scriptable makeIteratorResult( + Context cx, Scriptable scope, boolean done, Object value) { + Scriptable result = cx.newObject(scope); + ScriptableObject.putProperty(result, ES6Iterator.DONE_PROPERTY, done); + ScriptableObject.putProperty(result, ES6Iterator.VALUE_PROPERTY, value); + return result; + } + + /** + * Calls the return() method on an iterator if it exists. + * + * @param cx Current context + * @param scope Current scope + * @param iterator The iterator object + * @return The result of calling return(), or undefined if no return method + */ + public static Object callReturn(Context cx, Scriptable scope, Object iterator) { + if (!(iterator instanceof Scriptable)) { + return Undefined.instance; + } + + Scriptable iterScriptable = (Scriptable) iterator; + Object returnMethod = + ScriptableObject.getProperty(iterScriptable, ES6Iterator.RETURN_METHOD); + + if (returnMethod instanceof Callable) { + Callable callable = (Callable) returnMethod; + return callable.call(cx, scope, iterScriptable, ScriptRuntime.emptyArgs); + } + + return Undefined.instance; + } + + /** + * Gets the next value from an iterator. + * + * @param cx Current context + * @param scope Current scope + * @param iterator The iterator object + * @return The iterator result object + */ + public static Object next(Context cx, Scriptable scope, Object iterator) { + if (!(iterator instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator must be an object"); + } + + Scriptable iterScriptable = (Scriptable) iterator; + Object nextMethod = ScriptableObject.getProperty(iterScriptable, ES6Iterator.NEXT_METHOD); + + if (!(nextMethod instanceof Callable)) { + throw ScriptRuntime.typeError("Iterator missing next method"); + } + + Callable callable = (Callable) nextMethod; + return callable.call(cx, scope, iterScriptable, ScriptRuntime.emptyArgs); + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeES2025Iterator.java b/rhino/src/main/java/org/mozilla/javascript/NativeES2025Iterator.java new file mode 100644 index 0000000000..cc85812b22 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/NativeES2025Iterator.java @@ -0,0 +1,997 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript; + +/** ES2025 Iterator constructor and helper methods. */ +public class NativeES2025Iterator extends ScriptableObject { + private static final long serialVersionUID = 1L; + private static final String CLASS_NAME = "Iterator"; + + static void init(Context cx, ScriptableObject scope, boolean sealed) { + LambdaConstructor constructor = + new LambdaConstructor( + scope, + CLASS_NAME, + 0, + LambdaConstructor.CONSTRUCTOR_FUNCTION, + NativeES2025Iterator::constructor); + + constructor.defineConstructorMethod(scope, "from", 1, NativeES2025Iterator::js_from); + constructor.defineConstructorMethod(scope, "concat", 0, NativeES2025Iterator::js_concat); + + constructor.definePrototypeMethod( + scope, + SymbolKey.ITERATOR, + 0, + NativeES2025Iterator::js_iterator, + DONTENUM, + DONTENUM); + constructor.definePrototypeMethod( + scope, "map", 1, NativeES2025Iterator::js_map, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "filter", 1, NativeES2025Iterator::js_filter, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "take", 1, NativeES2025Iterator::js_take, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "drop", 1, NativeES2025Iterator::js_drop, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "flatMap", 1, NativeES2025Iterator::js_flatMap, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "reduce", 1, NativeES2025Iterator::js_reduce, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "toArray", 0, NativeES2025Iterator::js_toArray, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "forEach", 1, NativeES2025Iterator::js_forEach, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "some", 1, NativeES2025Iterator::js_some, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "every", 1, NativeES2025Iterator::js_every, DONTENUM, DONTENUM); + constructor.definePrototypeMethod( + scope, "find", 1, NativeES2025Iterator::js_find, DONTENUM, DONTENUM); + + constructor.definePrototypeProperty( + SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY); + + if (sealed) { + constructor.sealObject(); + ((ScriptableObject) constructor.getPrototypeProperty()).sealObject(); + } + + ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, DONTENUM); + } + + private NativeES2025Iterator() {} + + @Override + public String getClassName() { + return CLASS_NAME; + } + + private static Scriptable constructor(Context cx, Scriptable scope, Object[] args) { + throw ScriptRuntime.typeError("Iterator is not a constructor"); + } + + private static Object js_iterator( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return thisObj; + } + + private static Object js_from(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Object item = args.length > 0 ? args[0] : Undefined.instance; + + if (item == null || item == Undefined.instance) { + throw ScriptRuntime.typeError("Cannot convert undefined or null to iterator"); + } + + if (item instanceof ES2025IteratorPrototype) { + return item; + } + + if (item instanceof Scriptable) { + Scriptable scriptable = (Scriptable) item; + Object iteratorMethod = ScriptableObject.getProperty(scriptable, SymbolKey.ITERATOR); + + if (iteratorMethod != Scriptable.NOT_FOUND && iteratorMethod instanceof Callable) { + Callable callable = (Callable) iteratorMethod; + Object iterator = callable.call(cx, scope, scriptable, ScriptRuntime.emptyArgs); + return new WrappedIterator(cx, scope, iterator); + } + } + + throw ScriptRuntime.typeError("Object is not iterable"); + } + + private static Object js_concat( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable[] iterables = new Scriptable[args.length]; + Callable[] iteratorMethods = new Callable[args.length]; + + for (int i = 0; i < args.length; i++) { + Object item = args[i]; + + if (!(item instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator.concat requires iterable objects"); + } + + Scriptable itemObj = (Scriptable) item; + Object iteratorMethod = ScriptableObject.getProperty(itemObj, SymbolKey.ITERATOR); + + if (!(iteratorMethod instanceof Callable)) { + throw ScriptRuntime.typeError( + "Iterator.concat item at index " + i + " is not iterable"); + } + + iterables[i] = itemObj; + iteratorMethods[i] = (Callable) iteratorMethod; + } + + return new ConcatIterator(cx, scope, iterables, iteratorMethods); + } + + /** Validates that thisObj is a Scriptable, throws TypeError if not */ + private static Scriptable requireObjectCoercible(Object thisObj, String methodName) { + if (thisObj == null || !(thisObj instanceof Scriptable)) { + throw ScriptRuntime.typeError( + "Iterator.prototype." + methodName + " called on non-object"); + } + return (Scriptable) thisObj; + } + + private static Callable getNextMethod(Scriptable thisObj) { + Object next = ScriptableObject.getProperty(thisObj, "next"); + if (!(next instanceof Callable)) { + throw ScriptRuntime.typeError("Iterator must have a next method"); + } + return (Callable) next; + } + + private static Scriptable callNext( + Context cx, Scriptable scope, Scriptable thisObj, Callable nextMethod) { + Object result = nextMethod.call(cx, scope, thisObj, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + return (Scriptable) result; + } + + private static boolean isDone(Scriptable result) { + Object done = ScriptableObject.getProperty(result, "done"); + return ScriptRuntime.toBoolean(done); + } + + private static Object getValue(Scriptable result) { + return ScriptableObject.getProperty(result, "value"); + } + + private static Object js_toArray( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "toArray"); + Callable nextMethod = getNextMethod(iterator); + Scriptable array = cx.newArray(scope, 0); + int index = 0; + + while (true) { + Scriptable result = callNext(cx, scope, iterator, nextMethod); + if (isDone(result)) { + break; + } + array.put(index++, array, getValue(result)); + } + + return array; + } + + private static Object js_forEach( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "forEach"); + + Object fn = args.length > 0 ? args[0] : Undefined.instance; + if (!(fn instanceof Callable)) { + throw ScriptRuntime.typeError("forEach requires a function argument"); + } + Callable callback = (Callable) fn; + + Callable nextMethod = getNextMethod(iterator); + + long counter = 0; + while (true) { + Object result = nextMethod.call(cx, scope, iterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + break; + } + + Object value = ScriptableObject.getProperty(resultObj, "value"); + callback.call(cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {value, counter}); + counter++; + } + + return Undefined.instance; + } + + private static Object js_some(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "some"); + + Object predicate = args.length > 0 ? args[0] : Undefined.instance; + if (!(predicate instanceof Callable)) { + throw ScriptRuntime.typeError("some requires a function argument"); + } + Callable predicateFn = (Callable) predicate; + + Callable nextMethod = getNextMethod(iterator); + Object returnMethod = ScriptableObject.getProperty(iterator, "return"); + + long counter = 0; + while (true) { + Object result = nextMethod.call(cx, scope, iterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + return Boolean.FALSE; + } + + Object value = ScriptableObject.getProperty(resultObj, "value"); + Object testResult = + predicateFn.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {value, counter}); + + if (ScriptRuntime.toBoolean(testResult)) { + if (returnMethod instanceof Callable) { + ((Callable) returnMethod).call(cx, scope, iterator, ScriptRuntime.emptyArgs); + } + return Boolean.TRUE; + } + counter++; + } + } + + private static Object js_every( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "every"); + + Object predicate = args.length > 0 ? args[0] : Undefined.instance; + if (!(predicate instanceof Callable)) { + throw ScriptRuntime.typeError("every requires a function argument"); + } + Callable predicateFn = (Callable) predicate; + + Callable nextMethod = getNextMethod(iterator); + Object returnMethod = ScriptableObject.getProperty(iterator, "return"); + + long counter = 0; + while (true) { + Object result = nextMethod.call(cx, scope, iterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + return Boolean.TRUE; + } + + Object value = ScriptableObject.getProperty(resultObj, "value"); + Object testResult = + predicateFn.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {value, counter}); + + if (!ScriptRuntime.toBoolean(testResult)) { + if (returnMethod instanceof Callable) { + ((Callable) returnMethod).call(cx, scope, iterator, ScriptRuntime.emptyArgs); + } + return Boolean.FALSE; + } + counter++; + } + } + + private static Object js_find(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "find"); + + Object predicate = args.length > 0 ? args[0] : Undefined.instance; + if (!(predicate instanceof Callable)) { + throw ScriptRuntime.typeError("find requires a function argument"); + } + Callable predicateFn = (Callable) predicate; + + Callable nextMethod = getNextMethod(iterator); + Object returnMethod = ScriptableObject.getProperty(iterator, "return"); + + long counter = 0; + while (true) { + Object result = nextMethod.call(cx, scope, iterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + return Undefined.instance; + } + + Object value = ScriptableObject.getProperty(resultObj, "value"); + Object testResult = + predicateFn.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {value, counter}); + + if (ScriptRuntime.toBoolean(testResult)) { + if (returnMethod instanceof Callable) { + ((Callable) returnMethod).call(cx, scope, iterator, ScriptRuntime.emptyArgs); + } + return value; + } + counter++; + } + } + + private static Object js_reduce( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "reduce"); + + Object reducer = args.length > 0 ? args[0] : Undefined.instance; + if (!(reducer instanceof Callable)) { + throw ScriptRuntime.typeError("reduce requires a function argument"); + } + Callable reducerFn = (Callable) reducer; + + Callable nextMethod = getNextMethod(iterator); + + Object accumulator; + long counter; + + if (args.length >= 2) { + accumulator = args[1]; + counter = 0; + } else { + Object result = nextMethod.call(cx, scope, iterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + throw ScriptRuntime.typeError("Reduce of empty iterator with no initial value"); + } + + accumulator = ScriptableObject.getProperty(resultObj, "value"); + counter = 1; + } + + while (true) { + Object result = nextMethod.call(cx, scope, iterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + break; + } + + Object value = ScriptableObject.getProperty(resultObj, "value"); + accumulator = + reducerFn.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {accumulator, value, counter}); + counter++; + } + + return accumulator; + } + + private static Object js_map(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "map"); + + Object mapper = args.length > 0 ? args[0] : Undefined.instance; + if (!(mapper instanceof Callable)) { + throw ScriptRuntime.typeError("map requires a function argument"); + } + Callable mapperFn = (Callable) mapper; + + Callable nextMethod = getNextMethod(iterator); + return new MapIterator(cx, scope, iterator, nextMethod, mapperFn); + } + + private static Object js_filter( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "filter"); + + Object predicate = args.length > 0 ? args[0] : Undefined.instance; + if (!(predicate instanceof Callable)) { + throw ScriptRuntime.typeError("filter requires a function argument"); + } + Callable predicateFn = (Callable) predicate; + + Callable nextMethod = getNextMethod(iterator); + return new FilterIterator(cx, scope, iterator, nextMethod, predicateFn); + } + + private static Object js_take(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "take"); + + double limit = args.length > 0 ? ScriptRuntime.toNumber(args[0]) : Double.NaN; + + if (Double.isNaN(limit)) { + throw ScriptRuntime.rangeError("take limit must be a number"); + } + if (limit < 0) { + throw ScriptRuntime.rangeError("take limit must be non-negative"); + } + + long remaining = (long) Math.floor(limit); + Callable nextMethod = getNextMethod(iterator); + return new TakeIterator(cx, scope, iterator, nextMethod, remaining); + } + + private static Object js_drop(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "drop"); + + double limit = args.length > 0 ? ScriptRuntime.toNumber(args[0]) : Double.NaN; + + if (Double.isNaN(limit)) { + throw ScriptRuntime.rangeError("drop limit must be a number"); + } + if (limit < 0) { + throw ScriptRuntime.rangeError("drop limit must be non-negative"); + } + + long toSkip = (long) Math.floor(limit); + Callable nextMethod = getNextMethod(iterator); + return new DropIterator(cx, scope, iterator, nextMethod, toSkip); + } + + private static Object js_flatMap( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable iterator = requireObjectCoercible(thisObj, "flatMap"); + + Object mapper = args.length > 0 ? args[0] : Undefined.instance; + if (!(mapper instanceof Callable)) { + throw ScriptRuntime.typeError("flatMap requires a function argument"); + } + Callable mapperFn = (Callable) mapper; + + Callable nextMethod = getNextMethod(iterator); + return new FlatMapIterator(cx, scope, iterator, nextMethod, mapperFn); + } + + /** Base class for iterators that inherit from Iterator.prototype */ + abstract static class ES2025IteratorPrototype extends ScriptableObject { + private boolean isExecuting = false; + + ES2025IteratorPrototype() { + defineProperty( + "next", + new BaseFunction() { + @Override + public Object call( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + if (!(thisObj instanceof ES2025IteratorPrototype)) { + throw ScriptRuntime.typeError("next called on incompatible object"); + } + ES2025IteratorPrototype self = (ES2025IteratorPrototype) thisObj; + if (self.isExecuting) { + throw ScriptRuntime.typeError( + "Iterator Helper generator is already running"); + } + self.isExecuting = true; + try { + return self.next(cx, scope); + } finally { + self.isExecuting = false; + } + } + + @Override + public int getLength() { + return 0; + } + + @Override + public String getFunctionName() { + return "next"; + } + }, + DONTENUM); + } + + abstract Object next(Context cx, Scriptable scope); + + Object doReturn(Context cx, Scriptable scope, Object value) { + Scriptable result = cx.newObject(scope); + ScriptableObject.putProperty(result, "done", Boolean.TRUE); + ScriptableObject.putProperty(result, "value", value); + return result; + } + + Object doThrow(Context cx, Scriptable scope, Object value) { + if (value instanceof JavaScriptException) { + throw (JavaScriptException) value; + } else if (value instanceof RhinoException) { + throw (RhinoException) value; + } + throw ScriptRuntime.typeError(value.toString()); + } + + @Override + public String getClassName() { + return "Iterator"; + } + } + + /** + * Wrapper for iterators returned by Iterator.from() to ensure they inherit from + * Iterator.prototype + */ + private static class WrappedIterator extends ES2025IteratorPrototype { + private final Object wrappedIterator; + private final Callable nextMethod; + private Callable returnMethod; + private Callable throwMethod; + + WrappedIterator(Context cx, Scriptable scope, Object iterator) { + this.wrappedIterator = iterator; + + // Get the next method + if (iterator instanceof Scriptable) { + Scriptable iterScriptable = (Scriptable) iterator; + Object next = ScriptableObject.getProperty(iterScriptable, "next"); + if (next instanceof Callable) { + this.nextMethod = (Callable) next; + } else { + throw ScriptRuntime.typeError("Iterator missing next method"); + } + + // Get optional return method + Object ret = ScriptableObject.getProperty(iterScriptable, "return"); + if (ret instanceof Callable) { + this.returnMethod = (Callable) ret; + } + + // Get optional throw method + Object thr = ScriptableObject.getProperty(iterScriptable, "throw"); + if (thr instanceof Callable) { + this.throwMethod = (Callable) thr; + } + + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + Scriptable iteratorProto = ScriptableObject.getClassPrototype(topScope, "Iterator"); + if (iteratorProto != null) { + this.setPrototype(iteratorProto); + } + this.setParentScope(scope); + } else { + throw ScriptRuntime.typeError("Iterator must be an object"); + } + } + + @Override + Object next(Context cx, Scriptable scope) { + if (wrappedIterator instanceof Scriptable) { + return nextMethod.call( + cx, scope, (Scriptable) wrappedIterator, ScriptRuntime.emptyArgs); + } + throw ScriptRuntime.typeError("Invalid iterator"); + } + + @Override + Object doReturn(Context cx, Scriptable scope, Object value) { + if (returnMethod != null && wrappedIterator instanceof Scriptable) { + return returnMethod.call( + cx, scope, (Scriptable) wrappedIterator, new Object[] {value}); + } + return super.doReturn(cx, scope, value); + } + + @Override + Object doThrow(Context cx, Scriptable scope, Object value) { + if (throwMethod != null && wrappedIterator instanceof Scriptable) { + return throwMethod.call( + cx, scope, (Scriptable) wrappedIterator, new Object[] {value}); + } + return super.doThrow(cx, scope, value); + } + } + + /** Iterator returned by map() */ + private static class MapIterator extends ES2025IteratorPrototype { + private final Scriptable sourceIterator; + private final Callable nextMethod; + private final Callable mapper; + private long counter = 0; + + MapIterator( + Context cx, + Scriptable scope, + Scriptable sourceIterator, + Callable nextMethod, + Callable mapper) { + this.sourceIterator = sourceIterator; + this.nextMethod = nextMethod; + this.mapper = mapper; + + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + Scriptable iteratorProto = ScriptableObject.getClassPrototype(topScope, "Iterator"); + if (iteratorProto != null) { + this.setPrototype(iteratorProto); + } + this.setParentScope(scope); + } + + @Override + Object next(Context cx, Scriptable scope) { + Object result = nextMethod.call(cx, scope, sourceIterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + return result; + } + + Object value = ScriptableObject.getProperty(resultObj, "value"); + Object mappedValue = + mapper.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {value, counter}); + counter++; + + Scriptable newResult = cx.newObject(scope); + ScriptableObject.putProperty(newResult, "done", Boolean.FALSE); + ScriptableObject.putProperty(newResult, "value", mappedValue); + return newResult; + } + } + + /** Iterator returned by filter() */ + private static class FilterIterator extends ES2025IteratorPrototype { + private final Scriptable sourceIterator; + private final Callable nextMethod; + private final Callable predicate; + private long counter = 0; + + FilterIterator( + Context cx, + Scriptable scope, + Scriptable sourceIterator, + Callable nextMethod, + Callable predicate) { + this.sourceIterator = sourceIterator; + this.nextMethod = nextMethod; + this.predicate = predicate; + + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + Scriptable iteratorProto = ScriptableObject.getClassPrototype(topScope, "Iterator"); + if (iteratorProto != null) { + this.setPrototype(iteratorProto); + } + this.setParentScope(scope); + } + + @Override + Object next(Context cx, Scriptable scope) { + while (true) { + Object result = nextMethod.call(cx, scope, sourceIterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + return result; + } + + Object value = ScriptableObject.getProperty(resultObj, "value"); + Object testResult = + predicate.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {value, counter}); + counter++; + + if (ScriptRuntime.toBoolean(testResult)) { + return result; + } + } + } + } + + /** Iterator returned by take() */ + private static class TakeIterator extends ES2025IteratorPrototype { + private final Scriptable sourceIterator; + private final Callable nextMethod; + private long remaining; + + TakeIterator( + Context cx, + Scriptable scope, + Scriptable sourceIterator, + Callable nextMethod, + long remaining) { + this.sourceIterator = sourceIterator; + this.nextMethod = nextMethod; + this.remaining = remaining; + + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + Scriptable iteratorProto = ScriptableObject.getClassPrototype(topScope, "Iterator"); + if (iteratorProto != null) { + this.setPrototype(iteratorProto); + } + this.setParentScope(scope); + } + + @Override + Object next(Context cx, Scriptable scope) { + if (remaining <= 0) { + Object returnMethod = ScriptableObject.getProperty(sourceIterator, "return"); + if (returnMethod instanceof Callable) { + ((Callable) returnMethod) + .call(cx, scope, sourceIterator, ScriptRuntime.emptyArgs); + } + + Scriptable result = cx.newObject(scope); + ScriptableObject.putProperty(result, "done", Boolean.TRUE); + ScriptableObject.putProperty(result, "value", Undefined.instance); + return result; + } + + Object result = nextMethod.call(cx, scope, sourceIterator, ScriptRuntime.emptyArgs); + remaining--; + return result; + } + } + + /** Iterator returned by drop() */ + private static class DropIterator extends ES2025IteratorPrototype { + private final Scriptable sourceIterator; + private final Callable nextMethod; + private long toSkip; + + DropIterator( + Context cx, + Scriptable scope, + Scriptable sourceIterator, + Callable nextMethod, + long toSkip) { + this.sourceIterator = sourceIterator; + this.nextMethod = nextMethod; + this.toSkip = toSkip; + + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + Scriptable iteratorProto = ScriptableObject.getClassPrototype(topScope, "Iterator"); + if (iteratorProto != null) { + this.setPrototype(iteratorProto); + } + this.setParentScope(scope); + } + + @Override + Object next(Context cx, Scriptable scope) { + while (toSkip > 0) { + Object result = nextMethod.call(cx, scope, sourceIterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + return result; + } + toSkip--; + } + + return nextMethod.call(cx, scope, sourceIterator, ScriptRuntime.emptyArgs); + } + } + + /** Iterator returned by flatMap() */ + private static class FlatMapIterator extends ES2025IteratorPrototype { + private final Scriptable sourceIterator; + private final Callable nextMethod; + private final Callable mapper; + private long counter = 0; + private Scriptable innerIterator = null; + private Callable innerNextMethod = null; + + FlatMapIterator( + Context cx, + Scriptable scope, + Scriptable sourceIterator, + Callable nextMethod, + Callable mapper) { + this.sourceIterator = sourceIterator; + this.nextMethod = nextMethod; + this.mapper = mapper; + + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + Scriptable iteratorProto = ScriptableObject.getClassPrototype(topScope, "Iterator"); + if (iteratorProto != null) { + this.setPrototype(iteratorProto); + } + this.setParentScope(scope); + } + + @Override + Object next(Context cx, Scriptable scope) { + while (true) { + if (innerIterator != null) { + Object innerResult = + innerNextMethod.call(cx, scope, innerIterator, ScriptRuntime.emptyArgs); + if (!(innerResult instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable innerResultObj = (Scriptable) innerResult; + + Object innerDone = ScriptableObject.getProperty(innerResultObj, "done"); + if (!ScriptRuntime.toBoolean(innerDone)) { + return innerResult; + } + + innerIterator = null; + innerNextMethod = null; + } + + Object result = nextMethod.call(cx, scope, sourceIterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + return result; + } + + Object value = ScriptableObject.getProperty(resultObj, "value"); + Object mapped = + mapper.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {value, counter}); + counter++; + + if (mapped instanceof String || mapped instanceof ConsString) { + throw ScriptRuntime.typeError("flatMap mapper cannot return a string"); + } + + if (mapped instanceof Scriptable) { + Scriptable mappedObj = (Scriptable) mapped; + Object iteratorMethod = + ScriptableObject.getProperty(mappedObj, SymbolKey.ITERATOR); + + if (iteratorMethod instanceof Callable) { + Object iter = + ((Callable) iteratorMethod) + .call(cx, scope, mappedObj, ScriptRuntime.emptyArgs); + if (iter instanceof Scriptable) { + innerIterator = (Scriptable) iter; + Object next = ScriptableObject.getProperty(innerIterator, "next"); + if (next instanceof Callable) { + innerNextMethod = (Callable) next; + continue; + } + } + } + } + + throw ScriptRuntime.typeError("flatMap mapper must return an iterable"); + } + } + } + + /** Iterator returned by concat() */ + private static class ConcatIterator extends ES2025IteratorPrototype { + private final Scriptable[] iterables; + private final Callable[] iteratorMethods; + private int currentIndex = 0; + private Scriptable currentIterator = null; + private Callable currentNextMethod = null; + + ConcatIterator( + Context cx, Scriptable scope, Scriptable[] iterables, Callable[] iteratorMethods) { + this.iterables = iterables; + this.iteratorMethods = iteratorMethods; + + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + Scriptable iteratorProto = ScriptableObject.getClassPrototype(topScope, "Iterator"); + if (iteratorProto != null) { + this.setPrototype(iteratorProto); + } + this.setParentScope(scope); + } + + @Override + Object next(Context cx, Scriptable scope) { + while (true) { + if (currentIterator == null) { + if (currentIndex >= iterables.length) { + Scriptable result = cx.newObject(scope); + ScriptableObject.putProperty(result, "done", Boolean.TRUE); + ScriptableObject.putProperty(result, "value", Undefined.instance); + return result; + } + + Scriptable iterable = iterables[currentIndex]; + Callable iteratorMethod = iteratorMethods[currentIndex]; + + Object iterator = + iteratorMethod.call(cx, scope, iterable, ScriptRuntime.emptyArgs); + if (!(iterator instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator method must return an object"); + } + + currentIterator = (Scriptable) iterator; + Object next = ScriptableObject.getProperty(currentIterator, "next"); + if (!(next instanceof Callable)) { + throw ScriptRuntime.typeError("Iterator must have a next method"); + } + currentNextMethod = (Callable) next; + } + + Object result = + currentNextMethod.call(cx, scope, currentIterator, ScriptRuntime.emptyArgs); + if (!(result instanceof Scriptable)) { + throw ScriptRuntime.typeError("Iterator result must be an object"); + } + Scriptable resultObj = (Scriptable) result; + + Object done = ScriptableObject.getProperty(resultObj, "done"); + if (ScriptRuntime.toBoolean(done)) { + currentIterator = null; + currentNextMethod = null; + currentIndex++; + continue; + } + + return result; + } + } + + @Override + Object doReturn(Context cx, Scriptable scope, Object value) { + if (currentIterator != null) { + Object returnMethod = ScriptableObject.getProperty(currentIterator, "return"); + if (returnMethod instanceof Callable) { + ((Callable) returnMethod) + .call(cx, scope, currentIterator, new Object[] {value}); + } + } + return super.doReturn(cx, scope, value); + } + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java index 9ae8dce637..25792c3773 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java @@ -232,6 +232,11 @@ public static ScriptableObject initSafeStandardObjects( NativeIterator.init(cx, scope, sealed); // Also initializes NativeGenerator & ES6Generator + // ES2025 Iterator constructor + if (cx.getLanguageVersion() >= Context.VERSION_ECMASCRIPT) { + NativeES2025Iterator.init(cx, scope, sealed); + } + NativeArrayIterator.init(scope, sealed); NativeStringIterator.init(scope, sealed); registerRegExp(cx, scope, sealed); diff --git a/tests/testsrc/test262.properties b/tests/testsrc/test262.properties index c367b35bbd..f6044047b9 100644 --- a/tests/testsrc/test262.properties +++ b/tests/testsrc/test262.properties @@ -448,12 +448,10 @@ harness 23/116 (19.83%) isConstructor.js nativeFunctionMatcher.js -built-ins/AggregateError 2/25 (8.0%) - newtarget-proto-fallback.js - proto-from-ctor-realm.js - -built-ins/Array 257/3077 (8.35%) +built-ins/Array 259/3077 (8.42%) fromAsync 95/95 (100.0%) + from/iter-map-fn-err.js + from/iter-set-elem-prop-err.js length/define-own-prop-length-coercion-order-set.js prototype/at/coerced-index-resize.js {unsupported: [resizable-arraybuffer]} prototype/at/typed-array-resizable-buffer.js {unsupported: [resizable-arraybuffer]} @@ -1085,46 +1083,35 @@ built-ins/GeneratorPrototype 10/61 (16.39%) built-ins/Infinity 0/6 (0.0%) -built-ins/Iterator 392/432 (90.74%) - concat/arguments-checked-in-order.js +built-ins/Iterator 330/432 (76.39%) concat/fresh-iterator-result.js concat/get-iterator-method-only-once.js - concat/get-iterator-method-throws.js - concat/get-value-after-done.js - concat/inner-iterator-created-in-order.js - concat/is-function.js - concat/iterable-primitive-wrapper-objects.js - concat/length.js concat/many-arguments.js - concat/name.js - concat/next-method-called-with-zero-arguments.js - concat/next-method-returns-non-object.js - concat/next-method-returns-throwing-done.js - concat/next-method-returns-throwing-value.js concat/next-method-returns-throwing-value-done.js - concat/next-method-throws.js - concat/prop-desc.js - concat/proto.js - concat/result-is-iterator.js concat/return-is-forwarded.js concat/return-is-not-forwarded-after-exhaustion.js concat/return-is-not-forwarded-before-initial-start.js concat/return-method-called-with-zero-arguments.js - concat/single-argument.js - concat/throws-typeerror-when-generator-is-running-next.js concat/throws-typeerror-when-generator-is-running-return.js - concat/throws-typeerror-when-iterator-not-an-object.js - concat/zero-arguments.js - from 19/19 (100.0%) + from/get-next-method-only-once.js + from/get-next-method-throws.js + from/get-return-method-when-call-return.js + from/iterable-primitives.js + from/iterable-to-iterator-fallback.js + from/non-constructible.js + from/primitives.js + from/result-proto.js + from/return-method-calls-base-return-method.js + from/return-method-returns-iterator-result.js + from/return-method-throws-for-invalid-this.js + from/supports-iterator.js prototype/constructor 2/2 (100.0%) - prototype/drop/argument-effect-order.js prototype/drop/argument-validation-failure-closes-underlying.js prototype/drop/callable.js prototype/drop/exhaustion-does-not-call-return.js prototype/drop/get-next-method-only-once.js prototype/drop/get-next-method-throws.js prototype/drop/get-return-method-throws.js - prototype/drop/is-function.js prototype/drop/length.js prototype/drop/limit-equals-total.js prototype/drop/limit-greater-than-total.js @@ -1139,13 +1126,11 @@ built-ins/Iterator 392/432 (90.74%) prototype/drop/next-method-returns-throwing-value-done.js prototype/drop/next-method-throws.js prototype/drop/non-constructible.js - prototype/drop/prop-desc.js - prototype/drop/proto.js prototype/drop/result-is-iterator.js prototype/drop/return-is-forwarded.js prototype/drop/return-is-not-forwarded-after-exhaustion.js prototype/drop/this-non-callable-next.js - prototype/drop/this-plain-iterator.js + prototype/drop/this-non-object.js prototype/drop/throws-typeerror-when-generator-is-running.js prototype/drop/underlying-iterator-advanced-in-parallel.js prototype/drop/underlying-iterator-closed.js @@ -1155,7 +1140,6 @@ built-ins/Iterator 392/432 (90.74%) prototype/every/get-next-method-only-once.js prototype/every/get-next-method-throws.js prototype/every/get-return-method-throws.js - prototype/every/is-function.js prototype/every/iterator-already-exhausted.js prototype/every/iterator-has-no-return.js prototype/every/iterator-return-method-throws.js @@ -1175,17 +1159,14 @@ built-ins/Iterator 392/432 (90.74%) prototype/every/predicate-this.js prototype/every/predicate-throws.js prototype/every/predicate-throws-then-closing-iterator-also-throws.js - prototype/every/prop-desc.js - prototype/every/proto.js prototype/every/result-is-boolean.js - prototype/every/this-plain-iterator.js + prototype/every/this-non-object.js prototype/filter/argument-validation-failure-closes-underlying.js prototype/filter/callable.js prototype/filter/exhaustion-does-not-call-return.js prototype/filter/get-next-method-only-once.js prototype/filter/get-next-method-throws.js prototype/filter/get-return-method-throws.js - prototype/filter/is-function.js prototype/filter/iterator-already-exhausted.js prototype/filter/iterator-return-method-throws.js prototype/filter/length.js @@ -1202,13 +1183,11 @@ built-ins/Iterator 392/432 (90.74%) prototype/filter/predicate-this.js prototype/filter/predicate-throws.js prototype/filter/predicate-throws-then-closing-iterator-also-throws.js - prototype/filter/prop-desc.js - prototype/filter/proto.js prototype/filter/result-is-iterator.js prototype/filter/return-is-forwarded.js prototype/filter/return-is-not-forwarded-after-exhaustion.js prototype/filter/this-non-callable-next.js - prototype/filter/this-plain-iterator.js + prototype/filter/this-non-object.js prototype/filter/throws-typeerror-when-generator-is-running.js prototype/filter/underlying-iterator-advanced-in-parallel.js prototype/filter/underlying-iterator-closed.js @@ -1218,7 +1197,6 @@ built-ins/Iterator 392/432 (90.74%) prototype/find/get-next-method-only-once.js prototype/find/get-next-method-throws.js prototype/find/get-return-method-throws.js - prototype/find/is-function.js prototype/find/iterator-already-exhausted.js prototype/find/iterator-has-no-return.js prototype/find/iterator-return-method-throws.js @@ -1238,9 +1216,7 @@ built-ins/Iterator 392/432 (90.74%) prototype/find/predicate-this.js prototype/find/predicate-throws.js prototype/find/predicate-throws-then-closing-iterator-also-throws.js - prototype/find/prop-desc.js - prototype/find/proto.js - prototype/find/this-plain-iterator.js + prototype/find/this-non-object.js prototype/flatMap/argument-validation-failure-closes-underlying.js prototype/flatMap/callable.js prototype/flatMap/exhaustion-does-not-call-return.js @@ -1250,7 +1226,6 @@ built-ins/Iterator 392/432 (90.74%) prototype/flatMap/get-next-method-only-once.js prototype/flatMap/get-next-method-throws.js prototype/flatMap/get-return-method-throws.js - prototype/flatMap/is-function.js prototype/flatMap/iterable-primitives-are-not-flattened.js prototype/flatMap/iterable-to-iterator-fallback.js prototype/flatMap/iterator-already-exhausted.js @@ -1269,15 +1244,13 @@ built-ins/Iterator 392/432 (90.74%) prototype/flatMap/next-method-returns-throwing-value-done.js prototype/flatMap/next-method-throws.js prototype/flatMap/non-constructible.js - prototype/flatMap/prop-desc.js - prototype/flatMap/proto.js prototype/flatMap/result-is-iterator.js prototype/flatMap/return-is-forwarded-to-mapper-result.js prototype/flatMap/return-is-forwarded-to-underlying-iterator.js prototype/flatMap/return-is-not-forwarded-after-exhaustion.js prototype/flatMap/strings-are-not-flattened.js prototype/flatMap/this-non-callable-next.js - prototype/flatMap/this-plain-iterator.js + prototype/flatMap/this-non-object.js prototype/flatMap/throws-typeerror-when-generator-is-running.js prototype/flatMap/underlying-iterator-advanced-in-parallel.js prototype/flatMap/underlying-iterator-closed.js @@ -1291,7 +1264,6 @@ built-ins/Iterator 392/432 (90.74%) prototype/forEach/fn-throws-then-closing-iterator-also-throws.js prototype/forEach/get-next-method-only-once.js prototype/forEach/get-next-method-throws.js - prototype/forEach/is-function.js prototype/forEach/iterator-already-exhausted.js prototype/forEach/length.js prototype/forEach/name.js @@ -1301,17 +1273,14 @@ built-ins/Iterator 392/432 (90.74%) prototype/forEach/next-method-returns-throwing-value-done.js prototype/forEach/next-method-throws.js prototype/forEach/non-constructible.js - prototype/forEach/prop-desc.js - prototype/forEach/proto.js prototype/forEach/result-is-undefined.js - prototype/forEach/this-plain-iterator.js + prototype/forEach/this-non-object.js prototype/map/argument-validation-failure-closes-underlying.js prototype/map/callable.js prototype/map/exhaustion-does-not-call-return.js prototype/map/get-next-method-only-once.js prototype/map/get-next-method-throws.js prototype/map/get-return-method-throws.js - prototype/map/is-function.js prototype/map/iterator-already-exhausted.js prototype/map/iterator-return-method-throws.js prototype/map/length.js @@ -1326,24 +1295,21 @@ built-ins/Iterator 392/432 (90.74%) prototype/map/next-method-returns-throwing-value-done.js prototype/map/next-method-throws.js prototype/map/non-constructible.js - prototype/map/prop-desc.js - prototype/map/proto.js prototype/map/result-is-iterator.js prototype/map/return-is-forwarded-to-underlying-iterator.js prototype/map/return-is-not-forwarded-after-exhaustion.js prototype/map/returned-iterator-yields-mapper-return-values.js prototype/map/this-non-callable-next.js + prototype/map/this-non-object.js prototype/map/this-plain-iterator.js prototype/map/throws-typeerror-when-generator-is-running.js prototype/map/underlying-iterator-advanced-in-parallel.js prototype/map/underlying-iterator-closed.js prototype/map/underlying-iterator-closed-in-parallel.js - prototype/reduce/argument-effect-order.js prototype/reduce/argument-validation-failure-closes-underlying.js prototype/reduce/callable.js prototype/reduce/get-next-method-only-once.js prototype/reduce/get-next-method-throws.js - prototype/reduce/is-function.js prototype/reduce/iterator-already-exhausted-initial-value.js prototype/reduce/iterator-already-exhausted-no-initial-value.js prototype/reduce/iterator-yields-once-initial-value.js @@ -1357,21 +1323,18 @@ built-ins/Iterator 392/432 (90.74%) prototype/reduce/next-method-throws.js prototype/reduce/non-callable-reducer.js prototype/reduce/non-constructible.js - prototype/reduce/prop-desc.js - prototype/reduce/proto.js prototype/reduce/reducer-args-initial-value.js prototype/reduce/reducer-args-no-initial-value.js prototype/reduce/reducer-memo-can-be-any-type.js prototype/reduce/reducer-this.js prototype/reduce/reducer-throws.js prototype/reduce/reducer-throws-then-closing-iterator-also-throws.js - prototype/reduce/this-plain-iterator.js + prototype/reduce/this-non-object.js prototype/some/argument-validation-failure-closes-underlying.js prototype/some/callable.js prototype/some/get-next-method-only-once.js prototype/some/get-next-method-throws.js prototype/some/get-return-method-throws.js - prototype/some/is-function.js prototype/some/iterator-already-exhausted.js prototype/some/iterator-has-no-return.js prototype/some/iterator-return-method-throws.js @@ -1391,21 +1354,17 @@ built-ins/Iterator 392/432 (90.74%) prototype/some/predicate-this.js prototype/some/predicate-throws.js prototype/some/predicate-throws-then-closing-iterator-also-throws.js - prototype/some/prop-desc.js - prototype/some/proto.js prototype/some/result-is-boolean.js - prototype/some/this-plain-iterator.js + prototype/some/this-non-object.js prototype/Symbol.dispose 6/6 (100.0%) prototype/Symbol.iterator 5/5 (100.0%) prototype/Symbol.toStringTag 2/2 (100.0%) - prototype/take/argument-effect-order.js prototype/take/argument-validation-failure-closes-underlying.js prototype/take/callable.js prototype/take/exhaustion-calls-return.js prototype/take/get-next-method-only-once.js prototype/take/get-next-method-throws.js prototype/take/get-return-method-throws.js - prototype/take/is-function.js prototype/take/length.js prototype/take/limit-greater-than-or-equal-to-total.js prototype/take/limit-less-than-total.js @@ -1419,13 +1378,11 @@ built-ins/Iterator 392/432 (90.74%) prototype/take/next-method-returns-throwing-value-done.js prototype/take/next-method-throws.js prototype/take/non-constructible.js - prototype/take/prop-desc.js - prototype/take/proto.js prototype/take/result-is-iterator.js prototype/take/return-is-forwarded.js prototype/take/return-is-not-forwarded-after-exhaustion.js prototype/take/this-non-callable-next.js - prototype/take/this-plain-iterator.js + prototype/take/this-non-object.js prototype/take/throws-typeerror-when-generator-is-running.js prototype/take/underlying-iterator-advanced-in-parallel.js prototype/take/underlying-iterator-closed.js @@ -1433,7 +1390,6 @@ built-ins/Iterator 392/432 (90.74%) prototype/toArray/callable.js prototype/toArray/get-next-method-only-once.js prototype/toArray/get-next-method-throws.js - prototype/toArray/is-function.js prototype/toArray/iterator-already-exhausted.js prototype/toArray/length.js prototype/toArray/name.js @@ -1443,10 +1399,8 @@ built-ins/Iterator 392/432 (90.74%) prototype/toArray/next-method-returns-throwing-value-done.js prototype/toArray/next-method-throws.js prototype/toArray/non-constructible.js - prototype/toArray/prop-desc.js - prototype/toArray/proto.js - prototype/toArray/this-plain-iterator.js - length.js + prototype/toArray/this-non-object.js + prototype/initial-value.js proto-from-ctor-realm.js subclassable.js @@ -1480,9 +1434,13 @@ built-ins/JSON 42/165 (25.45%) stringify/value-bigint-tojson-receiver.js stringify/value-object-proxy.js -built-ins/Map 35/204 (17.16%) +built-ins/Map 39/204 (19.12%) prototype/getOrInsertComputed 19/19 (100.0%) prototype/getOrInsert 14/14 (100.0%) + iterator-close-after-set-failure.js + iterator-item-first-entry-returns-abrupt.js + iterator-item-second-entry-returns-abrupt.js + iterator-items-are-not-object-close-iterator.js proto-from-ctor-realm.js valid-keys.js @@ -1514,7 +1472,7 @@ built-ins/Number 4/335 (1.19%) S9.3.1_A3_T1_U180E.js {unsupported: [u180e]} S9.3.1_A3_T2_U180E.js {unsupported: [u180e]} -built-ins/Object 118/3410 (3.46%) +built-ins/Object 123/3410 (3.61%) assign/assignment-to-readonly-property-of-target-must-throw-a-typeerror-exception.js assign/source-own-prop-error.js assign/strings-and-symbol-order-proxy.js @@ -1559,6 +1517,11 @@ built-ins/Object 118/3410 (3.46%) freeze/proxy-no-ownkeys-returned-keys-order.js freeze/proxy-with-defineProperty-handler.js freeze/typedarray-backed-by-resizable-buffer.js {unsupported: [resizable-arraybuffer]} + fromEntries/iterator-closed-for-null-entry.js + fromEntries/iterator-closed-for-string-entry.js + fromEntries/iterator-closed-for-throwing-entry-key-accessor.js + fromEntries/iterator-closed-for-throwing-entry-key-tostring.js + fromEntries/iterator-closed-for-throwing-entry-value-accessor.js getOwnPropertyDescriptors/order-after-define-property.js getOwnPropertyDescriptors/proxy-no-ownkeys-returned-keys-order.js getOwnPropertyDescriptors/proxy-undefined-descriptor.js @@ -1634,17 +1597,20 @@ built-ins/Object 118/3410 (3.46%) proto-from-ctor-realm.js subclass-object-arg.js {unsupported: [class]} -built-ins/Promise 383/639 (59.94%) +built-ins/Promise 392/639 (61.35%) allSettled/capability-resolve-throws-reject.js {unsupported: [async]} allSettled/ctx-ctor.js {unsupported: [class]} allSettled/does-not-invoke-array-setters.js {unsupported: [async]} + allSettled/invoke-resolve-error-close.js allSettled/invoke-resolve-error-reject.js {unsupported: [async]} allSettled/invoke-resolve-get-error.js {unsupported: [async]} allSettled/invoke-resolve-get-error-reject.js {unsupported: [async]} allSettled/invoke-resolve-on-promises-every-iteration-of-custom.js {unsupported: [class, async]} allSettled/invoke-resolve-on-promises-every-iteration-of-promise.js {unsupported: [async]} allSettled/invoke-resolve-on-values-every-iteration-of-promise.js {unsupported: [async]} + allSettled/invoke-then-error-close.js allSettled/invoke-then-error-reject.js {unsupported: [async]} + allSettled/invoke-then-get-error-close.js allSettled/invoke-then-get-error-reject.js {unsupported: [async]} allSettled/iter-arg-is-false-reject.js {unsupported: [async]} allSettled/iter-arg-is-null-reject.js {unsupported: [async]} @@ -1698,13 +1664,16 @@ built-ins/Promise 383/639 (59.94%) all/capability-resolve-throws-reject.js {unsupported: [async]} all/ctx-ctor.js {unsupported: [class]} all/does-not-invoke-array-setters.js {unsupported: [async]} + all/invoke-resolve-error-close.js all/invoke-resolve-error-reject.js {unsupported: [async]} all/invoke-resolve-get-error.js {unsupported: [async]} all/invoke-resolve-get-error-reject.js {unsupported: [async]} all/invoke-resolve-on-promises-every-iteration-of-custom.js {unsupported: [class, async]} all/invoke-resolve-on-promises-every-iteration-of-promise.js {unsupported: [async]} all/invoke-resolve-on-values-every-iteration-of-promise.js {unsupported: [async]} + all/invoke-then-error-close.js all/invoke-then-error-reject.js {unsupported: [async]} + all/invoke-then-get-error-close.js all/invoke-then-get-error-reject.js {unsupported: [async]} all/iter-arg-is-false-reject.js {unsupported: [async]} all/iter-arg-is-null-reject.js {unsupported: [async]} @@ -1902,13 +1871,16 @@ built-ins/Promise 383/639 (59.94%) prototype/then/S25.4.5.3_A5.2_T1.js {unsupported: [async]} prototype/then/S25.4.5.3_A5.3_T1.js {unsupported: [async]} race/ctx-ctor.js {unsupported: [class]} + race/invoke-resolve-error-close.js race/invoke-resolve-error-reject.js {unsupported: [async]} race/invoke-resolve-get-error.js {unsupported: [async]} race/invoke-resolve-get-error-reject.js {unsupported: [async]} race/invoke-resolve-on-promises-every-iteration-of-custom.js {unsupported: [class, async]} race/invoke-resolve-on-promises-every-iteration-of-promise.js {unsupported: [async]} race/invoke-resolve-on-values-every-iteration-of-promise.js {unsupported: [async]} + race/invoke-then-error-close.js race/invoke-then-error-reject.js {unsupported: [async]} + race/invoke-then-get-error-close.js race/invoke-then-get-error-reject.js {unsupported: [async]} race/iter-arg-is-false-reject.js {unsupported: [async]} race/iter-arg-is-null-reject.js {unsupported: [async]} @@ -2252,7 +2224,7 @@ built-ins/RegExp 974/1868 (52.14%) built-ins/RegExpStringIteratorPrototype 0/17 (0.0%) -built-ins/Set 50/381 (13.12%) +built-ins/Set 51/381 (13.39%) prototype/difference/allows-set-like-class.js prototype/difference/receiver-not-set.js prototype/difference/result-order.js @@ -2302,6 +2274,7 @@ built-ins/Set 50/381 (13.12%) prototype/union/subclass-receiver-methods.js prototype/union/subclass-symbol-species.js proto-from-ctor-realm.js + set-iterator-close-after-add-failure.js valid-values.js built-ins/SetIteratorPrototype 0/11 (0.0%) @@ -2380,8 +2353,6 @@ built-ins/Symbol 5/94 (5.32%) keyFor/arg-non-symbol.js species/subclassing.js -built-ins/Temporal 4255/4255 (100.0%) - built-ins/ThrowTypeError 2/14 (14.29%) unique-per-realm-function-proto.js unique-per-realm-non-simple.js non-strict @@ -2901,14 +2872,19 @@ built-ins/Uint8Array 58/66 (87.88%) prototype/toHex/nonconstructor.js prototype/toHex/results.js -built-ins/WeakMap 40/141 (28.37%) +built-ins/WeakMap 44/141 (31.21%) prototype/getOrInsertComputed 22/22 (100.0%) prototype/getOrInsert 17/17 (100.0%) + iterator-close-after-set-failure.js + iterator-item-first-entry-returns-abrupt.js + iterator-item-second-entry-returns-abrupt.js + iterator-items-are-not-object-close-iterator.js proto-from-ctor-realm.js ~built-ins/WeakRef -built-ins/WeakSet 1/85 (1.18%) +built-ins/WeakSet 2/85 (2.35%) + iterator-close-after-add-failure.js proto-from-ctor-realm.js built-ins/decodeURI 1/55 (1.82%)