diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeError.java b/rhino/src/main/java/org/mozilla/javascript/NativeError.java index be88a74980..d0e19e1094 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeError.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeError.java @@ -7,6 +7,8 @@ package org.mozilla.javascript; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * The class of error objects @@ -55,12 +57,7 @@ static NativeError make(Context cx, Scriptable scope, IdFunctionObject ctorObj, } if (arglen >= 2) { if (args[1] instanceof NativeObject) { - NativeObject options = (NativeObject) args[1]; - Object cause = ScriptableObject.getProperty(options, "cause"); - if (cause != NOT_FOUND) { - ScriptableObject.putProperty(obj, "cause", cause); - obj.setAttributes("cause", DONTENUM); - } + installCause((NativeObject) args[1], obj); } else { ScriptableObject.putProperty(obj, "fileName", ScriptRuntime.toString(args[1])); if (arglen >= 3) { @@ -73,6 +70,60 @@ static NativeError make(Context cx, Scriptable scope, IdFunctionObject ctorObj, return obj; } + static NativeError makeAggregate( + Context cx, Scriptable scope, IdFunctionObject ctorObj, Object[] args) { + Scriptable proto = (Scriptable) ctorObj.get("prototype", ctorObj); + + NativeError obj = new NativeError(); + obj.setPrototype(proto); + obj.setParentScope(scope); + + int arglen = args.length; + if (arglen >= 1) { + if (arglen >= 2) { + if (!Undefined.isUndefined(args[1])) { + ScriptableObject.putProperty(obj, "message", ScriptRuntime.toString(args[1])); + obj.setAttributes("message", DONTENUM); + } + + if (arglen >= 3) { + if (args[2] instanceof NativeObject) { + installCause((NativeObject) args[2], obj); + } else { + ScriptableObject.putProperty( + obj, "fileName", ScriptRuntime.toString(args[2])); + if (arglen >= 4) { + ScriptableObject.putProperty( + obj, "lineNumber", ScriptRuntime.toInt32(args[3])); + } + } + } + } + + final Object iterator = ScriptRuntime.callIterator(args[0], cx, scope); + try (IteratorLikeIterable it = new IteratorLikeIterable(cx, scope, iterator)) { + List errors = new ArrayList<>(); + for (Object o : it) { + errors.add(o); + } + + Scriptable newArray = cx.newArray(scope, errors.toArray()); + obj.defineProperty("errors", newArray, DONTENUM); + } + } else { + throw ScriptRuntime.typeErrorById("msg.iterable.expected"); + } + return obj; + } + + static void installCause(NativeObject options, NativeError obj) { + Object cause = ScriptableObject.getProperty(options, "cause"); + if (cause != NOT_FOUND) { + ScriptableObject.putProperty(obj, "cause", cause); + obj.setAttributes("cause", DONTENUM); + } + } + @Override protected void fillConstructorProperties(IdFunctionObject ctor) { addIdFunctionProperty( diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeGlobal.java b/rhino/src/main/java/org/mozilla/javascript/NativeGlobal.java index 5daa4496ec..b672569186 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeGlobal.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeGlobal.java @@ -113,8 +113,12 @@ public static void init(Context cx, Scriptable scope, boolean sealed) { cx, scope, TopLevel.Builtins.Error, ScriptRuntime.emptyArgs); errorProto.defineProperty("name", name, DONTENUM); errorProto.defineProperty("message", "", DONTENUM); - IdFunctionObject ctor = - new IdFunctionObject(obj, FTAG, Id_new_CommonError, name, 1, scope); + IdFunctionObject ctor; + if (error == TopLevel.NativeErrors.AggregateError) { + ctor = new IdFunctionObject(obj, FTAG, Id_new_AggregateError, name, 2, scope); + } else { + ctor = new IdFunctionObject(obj, FTAG, Id_new_CommonError, name, 1, scope); + } ctor.markAsConstructor(errorProto); ctor.setPrototype(nativeError); errorProto.put("constructor", errorProto, ctor); @@ -203,6 +207,9 @@ public Object execIdCall( // The implementation of all the ECMA error constructors // (SyntaxError, TypeError, etc.) return NativeError.make(cx, scope, f, args); + + case Id_new_AggregateError: + return NativeError.makeAggregate(cx, scope, f, args); } } throw f.unknown(); @@ -760,5 +767,6 @@ private static int oneUcs4ToUtf8Char(byte[] utf8Buffer, int ucs4Char) { Id_unescape = 12, Id_uneval = 13, LAST_SCOPE_FUNCTION_ID = 13, - Id_new_CommonError = 14; + Id_new_CommonError = 14, + Id_new_AggregateError = 15; } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativePromise.java b/rhino/src/main/java/org/mozilla/javascript/NativePromise.java index 2ee1ad9b65..0e1208e2a2 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativePromise.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativePromise.java @@ -47,6 +47,8 @@ public static void init(Context cx, Scriptable scope, boolean sealed) { scope, "allSettled", 1, NativePromise::allSettled, DONTENUM, DONTENUM | READONLY); constructor.defineConstructorMethod( scope, "race", 1, NativePromise::race, DONTENUM, DONTENUM | READONLY); + constructor.defineConstructorMethod( + scope, "any", 1, NativePromise::any, DONTENUM, DONTENUM | READONLY); ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor); @@ -280,6 +282,43 @@ private static Object performRace( } } + private static Object any(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Capability cap = new Capability(cx, scope, thisObj); + Object arg = (args.length > 0 ? args[0] : Undefined.instance); + + IteratorLikeIterable iterable; + try { + Object maybeIterable = ScriptRuntime.callIterator(arg, cx, scope); + iterable = new IteratorLikeIterable(cx, scope, maybeIterable); + } catch (RhinoException re) { + cap.reject.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {getErrorObject(cx, scope, re)}); + return cap.promise; + } + + IteratorLikeIterable.Itr iterator = iterable.iterator(); + try { + PromiseAnyRejector rejector = new PromiseAnyRejector(iterator, thisObj, cap); + try { + return rejector.reject(cx, scope); + } finally { + if (!iterator.isDone()) { + iterable.close(); + } + } + } catch (RhinoException re) { + cap.reject.call( + cx, + scope, + Undefined.SCRIPTABLE_UNDEFINED, + new Object[] {getErrorObject(cx, scope, re)}); + return cap.promise; + } + } + // Promise.prototype.then private Object then( Context cx, Scriptable scope, LambdaConstructor defaultConstructor, Object[] args) { @@ -811,7 +850,109 @@ void finalResolution(Context cx, Scriptable scope) { } } + // This object keeps track of the state necessary to execute Promise.any + private static class PromiseAnyRejector { + // Limit the number of promises in Promise.any the same as it is in V8. + private static final int MAX_PROMISES = 1 << 21; + + final ArrayList errors = new ArrayList<>(); + int remainingElements = 1; + + IteratorLikeIterable.Itr iterator; + Scriptable thisObj; + Capability capability; + + PromiseAnyRejector(IteratorLikeIterable.Itr iter, Scriptable thisObj, Capability cap) { + this.iterator = iter; + this.thisObj = thisObj; + this.capability = cap; + } + + Object reject(Context topCx, Scriptable topScope) { + int index = 0; + // Do this first because we should catch any exception before + // invoking the iterator. + Callable resolve = + ScriptRuntime.getPropFunctionAndThis(thisObj, "resolve", topCx, topScope); + Scriptable storedThis = ScriptRuntime.lastStoredScriptable(topCx); + + // Iterate manually because we need to catch exceptions in a special way. + while (true) { + if (index == MAX_PROMISES) { + throw ScriptRuntime.rangeErrorById("msg.promise.any.toobig"); + } + boolean hasNext; + Object nextVal = Undefined.instance; + boolean nextOk = false; + try { + hasNext = iterator.hasNext(); + if (hasNext) { + nextVal = iterator.next(); + } + nextOk = true; + } finally { + if (!nextOk) { + iterator.setDone(true); + } + } + + if (!hasNext) { + if (--remainingElements == 0) { + Scriptable newArray = topCx.newArray(topScope, errors.toArray()); + NativeError error = + (NativeError) + topCx.newObject( + topScope, + "AggregateError", + new Object[] {newArray}); + throw new JavaScriptException(error, null, 0); + } + return capability.promise; + } + + errors.add(Undefined.instance); + + // Call "resolve" to get the next promise in the chain + Object nextPromise = + resolve.call(topCx, topScope, storedThis, new Object[] {nextVal}); + + // Create a resolution func that will stash its result in the right place + PromiseElementResolver eltResolver = new PromiseElementResolver(index); + LambdaFunction rejectFunc = + new LambdaFunction( + topScope, + 1, + (Context cx, + Scriptable scope, + Scriptable thisObj, + Object[] args) -> { + Object value = (args.length > 0 ? args[0] : Undefined.instance); + return eltResolver.reject(cx, scope, value, this); + }); + remainingElements++; + + // Call "then" on the promise with the resolution func + Callable thenFunc = + ScriptRuntime.getPropFunctionAndThis(nextPromise, "then", topCx, topScope); + thenFunc.call( + topCx, + topScope, + ScriptRuntime.lastStoredScriptable(topCx), + new Object[] {capability.resolve, rejectFunc}); + index++; + } + } + + void finalRejection(Context cx, Scriptable scope) { + Scriptable newArray = cx.newArray(scope, errors.toArray()); + NativeError error = + (NativeError) cx.newObject(scope, "AggregateError", new Object[] {newArray}); + capability.reject.call(cx, scope, Undefined.SCRIPTABLE_UNDEFINED, new Object[] {error}); + } + } + // This object keeps track of the state necessary to resolve one element in Promise.all + // and Promise.any private static class PromiseElementResolver { private boolean alreadyCalled = false; @@ -832,5 +973,17 @@ Object resolve(Context cx, Scriptable scope, Object result, PromiseAllResolver r } return Undefined.instance; } + + Object reject(Context cx, Scriptable scope, Object result, PromiseAnyRejector rejector) { + if (alreadyCalled) { + return Undefined.instance; + } + alreadyCalled = true; + rejector.errors.set(index, result); + if (--rejector.remainingElements == 0) { + rejector.finalRejection(cx, scope); + } + return Undefined.instance; + } } } diff --git a/rhino/src/main/java/org/mozilla/javascript/TopLevel.java b/rhino/src/main/java/org/mozilla/javascript/TopLevel.java index f41b54d6dc..6a4505b1c1 100644 --- a/rhino/src/main/java/org/mozilla/javascript/TopLevel.java +++ b/rhino/src/main/java/org/mozilla/javascript/TopLevel.java @@ -61,6 +61,8 @@ public enum Builtins { /** An enumeration of built-in native errors. [ECMAScript 5 - 15.11.6] */ enum NativeErrors { + /** The AggregateError */ + AggregateError, /** Basic Error */ Error, /** The native EvalError. */ diff --git a/rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties b/rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties index 642491e89c..7410230818 100644 --- a/rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties +++ b/rhino/src/main/resources/org/mozilla/javascript/resources/Messages.properties @@ -964,5 +964,12 @@ msg.promise.capability.state =\ msg.promise.all.toobig =\ Too many inputs to Promise.all +msg.promise.any.toobig =\ + Too many inputs to Promise.any + msg.typed.array.ctor.incompatible = \ Method %TypedArray%.prototype.{0} called on incompatible receiver + +# NativeError +msg.iterable.expected =\ + Expected the first argument to be iterable \ No newline at end of file diff --git a/tests/testsrc/test262.properties b/tests/testsrc/test262.properties index bda92a6188..b0e8db962c 100644 --- a/tests/testsrc/test262.properties +++ b/tests/testsrc/test262.properties @@ -1050,9 +1050,14 @@ built-ins/Math 51/326 (15.64%) built-ins/NaN 0/6 (0.0%) -built-ins/NativeErrors 43/117 (36.75%) - AggregateError/prototype 6/6 (100.0%) - AggregateError 19/19 (100.0%) +built-ins/NativeErrors 25/117 (21.37%) + AggregateError/errors-iterabletolist-failures.js + AggregateError/is-a-constructor.js {unsupported: [Reflect.construct]} + AggregateError/message-tostring-abrupt.js + AggregateError/message-tostring-abrupt-symbol.js + AggregateError/newtarget-proto-custom.js {unsupported: [Reflect.construct]} + AggregateError/newtarget-proto-fallback.js + AggregateError/proto-from-ctor-realm.js {unsupported: [Reflect]} EvalError/prototype/not-error-object.js EvalError/is-a-constructor.js {unsupported: [Reflect.construct]} EvalError/proto-from-ctor-realm.js {unsupported: [Reflect]} @@ -1098,7 +1103,7 @@ built-ins/Number 24/335 (7.16%) S9.3.1_A3_T1_U180E.js {unsupported: [u180e]} S9.3.1_A3_T2_U180E.js {unsupported: [u180e]} -built-ins/Object 218/3403 (6.41%) +built-ins/Object 217/3403 (6.38%) assign/assignment-to-readonly-property-of-target-must-throw-a-typeerror-exception.js assign/not-a-constructor.js {unsupported: [Reflect.construct]} assign/source-own-prop-desc-missing.js {unsupported: [Proxy]} @@ -1282,7 +1287,6 @@ built-ins/Object 218/3403 (6.41%) seal/not-a-constructor.js {unsupported: [Reflect.construct]} seal/proxy-no-ownkeys-returned-keys-order.js {unsupported: [Proxy, Reflect]} seal/proxy-with-defineProperty-handler.js {unsupported: [Proxy, Reflect]} - seal/seal-aggregateerror.js seal/seal-asyncarrowfunction.js seal/seal-asyncfunction.js seal/seal-asyncgeneratorfunction.js @@ -1304,7 +1308,7 @@ built-ins/Object 218/3403 (6.41%) proto-from-ctor-realm.js {unsupported: [Reflect]} subclass-object-arg.js {unsupported: [Reflect.construct, Reflect, class]} -built-ins/Promise 429/631 (67.99%) +built-ins/Promise 406/631 (64.34%) allSettled/capability-resolve-throws-reject.js {unsupported: [async]} allSettled/ctx-ctor.js {unsupported: [class]} allSettled/does-not-invoke-array-setters.js {unsupported: [async]} @@ -1426,16 +1430,10 @@ built-ins/Promise 429/631 (67.99%) all/S25.4.4.1_A8.1_T1.js {unsupported: [async]} all/S25.4.4.1_A8.2_T1.js {unsupported: [async]} all/S25.4.4.1_A8.2_T2.js {unsupported: [async]} - any/call-reject-element-after-return.js - any/call-reject-element-items.js - any/capability-executor-called-twice.js - any/capability-executor-not-callable.js any/capability-reject-throws-no-close.js {unsupported: [async]} any/capability-resolve-throws-no-close.js {unsupported: [async]} any/capability-resolve-throws-reject.js {unsupported: [async]} any/ctx-ctor.js {unsupported: [class]} - any/ctx-ctor-throws.js - any/ctx-non-ctor.js any/invoke-resolve.js {unsupported: [async]} any/invoke-resolve-error-close.js {unsupported: [async]} any/invoke-resolve-error-reject.js {unsupported: [async]} @@ -1447,14 +1445,12 @@ built-ins/Promise 429/631 (67.99%) any/invoke-resolve-on-promises-every-iteration-of-promise.js {unsupported: [async]} any/invoke-resolve-on-values-every-iteration-of-custom.js {unsupported: [class, async]} any/invoke-resolve-on-values-every-iteration-of-promise.js {unsupported: [async]} - any/invoke-resolve-return.js any/invoke-then.js {unsupported: [async]} any/invoke-then-error-close.js {unsupported: [async]} any/invoke-then-error-reject.js {unsupported: [async]} any/invoke-then-get-error-close.js {unsupported: [async]} any/invoke-then-get-error-reject.js {unsupported: [async]} any/invoke-then-on-promises-every-iteration.js {unsupported: [async]} - any/is-function.js any/iter-arg-is-empty-iterable-reject.js {unsupported: [async]} any/iter-arg-is-empty-string-reject.js {unsupported: [async]} any/iter-arg-is-error-object-reject.js {unsupported: [async]} @@ -1484,28 +1480,15 @@ built-ins/Promise 429/631 (67.99%) any/iter-returns-undefined-reject.js {unsupported: [async]} any/iter-step-err-no-close.js {unsupported: [async]} any/iter-step-err-reject.js {unsupported: [async]} - any/length.js - any/name.js - any/new-reject-function.js any/not-a-constructor.js {unsupported: [Reflect.construct]} - any/prop-desc.js any/reject-all-mixed.js {unsupported: [async]} any/reject-deferred.js {unsupported: [async]} - any/reject-element-function-extensible.js - any/reject-element-function-length.js - any/reject-element-function-name.js - any/reject-element-function-nonconstructor.js any/reject-element-function-property-order.js - any/reject-element-function-prototype.js - any/reject-from-same-thenable.js any/reject-ignored-deferred.js {unsupported: [async]} any/reject-ignored-immed.js {unsupported: [async]} any/reject-immed.js {unsupported: [async]} - any/resolve-before-loop-exit.js - any/resolve-before-loop-exit-from-same.js any/resolve-from-reject-catch.js {unsupported: [async]} any/resolve-from-resolve-reject-catch.js {unsupported: [async]} - any/resolve-from-same-thenable.js any/resolve-ignores-late-rejection.js {unsupported: [async]} any/resolve-ignores-late-rejection-deferred.js {unsupported: [async]} any/resolve-non-callable.js {unsupported: [async]} @@ -1515,8 +1498,6 @@ built-ins/Promise 429/631 (67.99%) any/resolved-sequence-extra-ticks.js {unsupported: [async]} any/resolved-sequence-mixed.js {unsupported: [async]} any/resolved-sequence-with-rejections.js {unsupported: [async]} - any/returns-promise.js - any/species-get-error.js prototype/catch/not-a-constructor.js {unsupported: [Reflect.construct]} prototype/catch/S25.4.5.1_A3.1_T1.js {unsupported: [async]} prototype/catch/S25.4.5.1_A3.1_T2.js {unsupported: [async]}