From ada10bce17ac16b308a3e5565988142407757729 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Tue, 13 Jul 2021 20:09:21 +0000 Subject: [PATCH 1/2] WIP: Base implementation --- js/src/builtin/Promise.cpp | 316 ++++++++++++++++++++++++++++++++ js/src/vm/CommonPropertyNames.h | 1 + 2 files changed, 317 insertions(+) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 9b461e487e..8ead6d9e7a 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -1529,12 +1529,63 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) return true; } +static MOZ_MUST_USE bool PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, + HandleObject C, HandleObject promiseObj, + HandleObject resolve, HandleObject reject, + bool* done); + +// ES2020 +static bool +Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedValue iterable(cx, args.get(0)); + RootedValue CVal(cx, args.thisv()); + if (!CVal.isObject()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, + "Receiver of Promise.allSettled call"); + return false; + } + + RootedObject C(cx, &CVal.toObject()); + RootedObject resultPromise(cx); + RootedObject resolve(cx); + RootedObject reject(cx); + if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + return false; + + JS::ForOfIterator iter(cx); + if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) + return AbruptRejectPromise(cx, args, resultPromise, reject); + + if (!iter.valueIsIterable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, + "Argument of Promise.allSettled"); + return AbruptRejectPromise(cx, args, resultPromise, reject); + } + + bool done; + bool result = PerformPromiseAllSettled(cx, iter, C, resultPromise, resolve, reject, &done); + + if (!result) { + if (!done) + iter.closeThrow(); + + return AbruptRejectPromise(cx, args, resultPromise, reject); + } + + args.rval().setObject(*resultPromise); + return true; +} + static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, HandleValue onRejected_, HandleObject resultPromise, HandleObject resolve, HandleObject reject); static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); +static bool PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); +static bool PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp); // Unforgeable version of ES2016, 25.4.4.1. MOZ_MUST_USE JSObject* @@ -1848,6 +1899,117 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, } } +static MOZ_MUST_USE bool +PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + HandleObject promiseObj, HandleObject resolve, HandleObject reject, + bool* done) +{ + *done = false; + + RootedObject unwrappedPromiseObj(cx); + if (IsWrapper(promiseObj)) { + unwrappedPromiseObj = CheckedUnwrap(promiseObj); + MOZ_ASSERT(unwrappedPromiseObj); + } + + RootedValue CVal(cx, ObjectValue(*C)); + + RootedObject valuesArray(cx); + if (unwrappedPromiseObj) { + JSAutoCompartment ac(cx, unwrappedPromiseObj); + valuesArray = NewDenseFullyAllocatedArray(cx, 0); + } else { + valuesArray = NewDenseFullyAllocatedArray(cx, 0); + } + if (!valuesArray) + return false; + + RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); + if (!cx->compartment()->wrap(cx, &valuesArrayVal)) + return false; + + Rooted dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj, + valuesArrayVal, resolve)); + if (!dataHolder) + return false; + RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); + + uint32_t index = 0; + + RootedValue nextValue(cx); + RootedId indexId(cx); + + while (true) { + if (!iterator.next(&nextValue, done)) { + *done = true; + return false; + } + + if (*done) { + int32_t remainingCount = dataHolder->decreaseRemainingCount(); + if (remainingCount == 0) { + return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, + promiseObj); + } + return true; + } + + { // Scoped for AutoCompartment + JSAutoCompartment ac(cx, valuesArray); + indexId = INT_TO_JSID(index); + if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) + return false; + } + + RootedValue nextPromise(cx); + RootedValue staticResolve(cx); + if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) + return false; + RootedValue staticReject(cx); + if (!GetProperty(cx, CVal, cx->names().reject, &staticReject)) + return false; + + FixedInvokeArgs<1> resolveArgs(cx); + resolveArgs[0].set(nextValue); + if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise)) + return false; + FixedInvokeArgs<1> rejectArgs(cx); + rejectArgs[0].set(nextValue); + if (!Call(cx, staticReject, CVal, rejectArgs, &nextPromise)) + return false; + + RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllSettledResolveElementFunction, + 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, + GenericObject)); + if (!resolveFunc) + return false; + RootedFunction rejectFunc(cx, NewNativeFunction(cx, PromiseAllSettledRejectElementFunction, + 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, + GenericObject)); + if (!rejectFunc) + return false; + + resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); + resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, + Int32Value(index)); + rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); + rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, + Int32Value(index)); + + dataHolder->increaseRemainingCount(); + + RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); + RootedValue rejectFunVal(cx, ObjectValue(*rejectFunc)); + if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal)) + return false; + + index++; + MOZ_ASSERT(index > 0); + } +} + // ES2016, 25.4.4.1.2. static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) @@ -1921,6 +2083,159 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) return true; } +// ES2020. +static bool +PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedFunction resolve(cx, &args.callee().as()); + RootedValue xVal(cx, args.get(0)); + RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data)); + + if (dataVal.isUndefined()) { + args.rval().setUndefined(); + return true; + } + + Rooted data(cx, &dataVal.toObject().as()); + + resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); + + int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) + .toInt32(); + + RootedValue valuesVal(cx, data->valuesArray()); + RootedObject valuesObj(cx, &valuesVal.toObject()); + bool valuesListIsWrapped = false; + if (IsWrapper(valuesObj)) { + valuesListIsWrapped = true; + // See comment for PerformPromiseAll, step 3 for why we unwrap here. + valuesObj = UncheckedUnwrap(valuesObj); + } + NativeObject* values = &valuesObj->as(); + + // The index is guaranteed to be initialized to `undefined`. + if (valuesListIsWrapped) { + AutoCompartment ac(cx, values); + if (!cx->compartment()->wrap(cx, &xVal)) + return false; + } + + RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); + if (!obj) { + return false; + } + RootedId id(cx, NameToId(cx->names().status)); + RootedValue statusValue(cx); + statusValue.setString(cx->names().fulfilled); + if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) { + return false; + } + id = NameToId(cx->names().value); + if (!::JS_DefinePropertyById(cx, obj, id, xVal, JSPROP_ENUMERATE)) { + return false; + } + + RootedValue objVal(cx, ObjectValue(*obj)); +/* if (needsWrapping) { + AutoRealm ar(cx, valuesObj); + if (!cx->compartment()->wrap(cx, &objVal)) { + return false; + } + } */ + values->setDenseElement(index, objVal); + + uint32_t remainingCount = data->decreaseRemainingCount(); + + if (remainingCount == 0) { + RootedObject resolveAllFun(cx, data->resolveObj()); + RootedObject promiseObj(cx, data->promiseObj()); + if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) + return false; + } + + args.rval().setUndefined(); + return true; +} + +static bool +PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedFunction resolve(cx, &args.callee().as()); + RootedValue xVal(cx, args.get(0)); + RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data)); + + if (dataVal.isUndefined()) { + args.rval().setUndefined(); + return true; + } + + Rooted data(cx, &dataVal.toObject().as()); + + resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); + + int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) + .toInt32(); + + RootedValue valuesVal(cx, data->valuesArray()); + RootedObject valuesObj(cx, &valuesVal.toObject()); + bool valuesListIsWrapped = false; + if (IsWrapper(valuesObj)) { + valuesListIsWrapped = true; + // See comment for PerformPromiseAll, step 3 for why we unwrap here. + valuesObj = UncheckedUnwrap(valuesObj); + } + NativeObject* values = &valuesObj->as(); + + // The index is guaranteed to be initialized to `undefined`. + if (valuesListIsWrapped) { + AutoCompartment ac(cx, values); + if (!cx->compartment()->wrap(cx, &xVal)) + return false; + } + + RootedPlainObject obj(cx, NewBuiltinClassInstance(cx)); + if (!obj) { + return false; + } + RootedId id(cx, NameToId(cx->names().status)); + RootedValue statusValue(cx); + statusValue.setString(cx->names().rejected); + if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) { + return false; + } + RootedValue resultValue(cx); + id = NameToId(cx->names().reason); + if (!::JS_DefinePropertyById(cx, obj, id, xVal, JSPROP_ENUMERATE)) { + return false; + } + + RootedValue objVal(cx, ObjectValue(*obj)); +/* if (needsWrapping) { + AutoRealm ar(cx, valuesObj); + if (!cx->compartment()->wrap(cx, &objVal)) { + return false; + } + } */ + values->setDenseElement(index, objVal); + + + uint32_t remainingCount = data->decreaseRemainingCount(); + + if (remainingCount == 0) { + RootedObject resolveAllFun(cx, data->resolveObj()); + RootedObject promiseObj(cx, data->promiseObj()); + if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) + return false; + } + + args.rval().setUndefined(); + return true; +} + static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, HandleObject promiseObj, HandleObject resolve, HandleObject reject, @@ -3246,6 +3561,7 @@ static const JSPropertySpec promise_properties[] = { static const JSFunctionSpec promise_static_methods[] = { JS_FN("all", Promise_static_all, 1, 0), + JS_FN("allSettled", Promise_static_allSettled, 1, 0), JS_FN("race", Promise_static_race, 1, 0), JS_FN("reject", Promise_reject, 1, 0), JS_FN("resolve", Promise_static_resolve, 1, 0), diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 7df7563b5b..445b8a6bb3 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -325,6 +325,7 @@ macro(startTimestamp, startTimestamp, "startTimestamp") \ macro(state, state, "state") \ macro(static, static_, "static") \ + macro(status, status, "status") \ macro(std_Function_apply, std_Function_apply, "std_Function_apply") \ macro(sticky, sticky, "sticky") \ macro(StringIterator, StringIterator, "String Iterator") \ From 495db8d7bc03e1e5e48934393fca9c274e50ec75 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Thu, 15 Jul 2021 03:08:11 -0500 Subject: [PATCH 2/2] Issue #1725 - Fix logic in PerformPromiseAllSettled. This function essentially appears to work as written, there was an issue with it returning false too early in some situations that prevented it from working properly. --- js/src/builtin/Promise.cpp | 55 ++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 8ead6d9e7a..faba010950 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -1963,40 +1963,43 @@ PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObjec RootedValue nextPromise(cx); RootedValue staticResolve(cx); - if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) - return false; - RootedValue staticReject(cx); - if (!GetProperty(cx, CVal, cx->names().reject, &staticReject)) + RootedValue staticReject(cx); + + // Because Promise.allSettled can continue whether the promise is fulfilled or rejected, we + // should only return false if neither condition is true. + + if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve) && + !GetProperty(cx, CVal, cx->names().reject, &staticReject)) return false; FixedInvokeArgs<1> resolveArgs(cx); resolveArgs[0].set(nextValue); - if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise)) - return false; FixedInvokeArgs<1> rejectArgs(cx); rejectArgs[0].set(nextValue); - if (!Call(cx, staticReject, CVal, rejectArgs, &nextPromise)) + if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise) && + !Call(cx, staticReject, CVal, rejectArgs, &nextPromise)) return false; - RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllSettledResolveElementFunction, - 1, nullptr, - gc::AllocKind::FUNCTION_EXTENDED, - GenericObject)); - if (!resolveFunc) - return false; - RootedFunction rejectFunc(cx, NewNativeFunction(cx, PromiseAllSettledRejectElementFunction, - 1, nullptr, - gc::AllocKind::FUNCTION_EXTENDED, - GenericObject)); - if (!rejectFunc) - return false; - - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, - Int32Value(index)); - rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); - rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, - Int32Value(index)); + + RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllSettledResolveElementFunction, + 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, + GenericObject)); + + RootedFunction rejectFunc(cx, NewNativeFunction(cx, PromiseAllSettledRejectElementFunction, + 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, + GenericObject)); + if (!resolveFunc && !rejectFunc) { + return false; + } + + resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); + resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, + Int32Value(index)); + rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); + rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, + Int32Value(index)); dataHolder->increaseRemainingCount();