diff options
Diffstat (limited to 'js/src')
-rw-r--r-- | js/src/builtin/Promise.cpp | 758 |
1 files changed, 440 insertions, 318 deletions
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 57f7cf63a8..6b2f10517a 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -80,14 +80,9 @@ enum RejectFunctionSlots { RejectFunctionSlot_ResolveFunction, }; -enum PromiseAllResolveElementFunctionSlots { - PromiseAllResolveElementFunctionSlot_Data = 0, - PromiseAllResolveElementFunctionSlot_ElementIndex, -}; - -enum PromiseAllSettledElementFunctionSlots { - PromiseAllSettledElementFunctionSlot_Data = 0, - PromiseAllSettledElementFunctionSlot_ElementIndex, +enum PromiseCombinatorElementFunctionSlots { + PromiseCombinatorElementFunctionSlot_Data = 0, + PromiseCombinatorElementFunctionSlot_ElementIndex, }; enum ReactionJobSlots { @@ -124,14 +119,6 @@ enum BuiltinThenableJobSlots { BuiltinThenableJobSlot_Thenable, }; -enum PromiseAllDataHolderSlots { - PromiseAllDataHolderSlot_Promise = 0, - PromiseAllDataHolderSlot_RemainingElements, - PromiseAllDataHolderSlot_ValuesArray, - PromiseAllDataHolderSlot_ResolveFunction, - PromiseAllDataHolderSlots, -}; - struct PromiseCapability { JSObject* promise = nullptr; JSObject* resolve = nullptr; @@ -193,53 +180,196 @@ class MutableWrappedPtrOperations<PromiseCapability, Wrapper> } // namespace js -class PromiseAllDataHolder : public NativeObject +struct PromiseCombinatorElements; + +class PromiseCombinatorDataHolder : public NativeObject { + enum { + Slot_Promise = 0, + Slot_RemainingElements, + Slot_ValuesArray, + Slot_ResolveOrRejectFunction, + SlotsCount, + }; + public: static const Class class_; - JSObject* promiseObj() { return &getFixedSlot(PromiseAllDataHolderSlot_Promise).toObject(); } - JSObject* resolveObj() { - return &getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).toObject(); + JSObject* promiseObj() { return &getFixedSlot(Slot_Promise).toObject(); } + JSObject* resolveOrRejectObj() { + return &getFixedSlot(Slot_ResolveOrRejectFunction).toObject(); } - Value valuesArray() { return getFixedSlot(PromiseAllDataHolderSlot_ValuesArray); } + Value valuesArray() { return getFixedSlot(Slot_ValuesArray); } int32_t remainingCount() { - return getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); + return getFixedSlot(Slot_RemainingElements).toInt32(); } int32_t increaseRemainingCount() { - int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); + int32_t remainingCount = getFixedSlot(Slot_RemainingElements).toInt32(); remainingCount++; - setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount)); + setFixedSlot(Slot_RemainingElements, Int32Value(remainingCount)); return remainingCount; } int32_t decreaseRemainingCount() { - int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); + int32_t remainingCount = getFixedSlot(Slot_RemainingElements).toInt32(); remainingCount--; - setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount)); + MOZ_ASSERT(remainingCount >= 0, "unpaired calls to decreaseRemainingCount"); + setFixedSlot(Slot_RemainingElements, Int32Value(remainingCount)); return remainingCount; } + + static PromiseCombinatorDataHolder* New(JSContext* cx, + HandleObject resultPromise, + Handle<PromiseCombinatorElements> elements, + HandleObject resolveOrReject); }; -const Class PromiseAllDataHolder::class_ = { - "PromiseAllDataHolder", - JSCLASS_HAS_RESERVED_SLOTS(PromiseAllDataHolderSlots) +const Class PromiseCombinatorDataHolder::class_ = { + "PromiseCombinatorDataHolder", + JSCLASS_HAS_RESERVED_SLOTS(SlotsCount) +}; + +// Smart pointer to the "F.[[Values]]" part of the state of a Promise.all or +// Promise.allSettled invocation, or the "F.[[Errors]]" part of the state of a +// Promise.any invocation. Copes with compartment issues when setting an +// element. +struct MOZ_STACK_CLASS PromiseCombinatorElements final +{ + // Object value holding the elements array. The object can be a wrapper. + Value value; + + // Unwrapped elements array. May not belong to the current compartment! + ArrayObject* unwrappedArray = nullptr; + + // Set to true if the |setElement| method needs to wrap its input value. + bool setElementNeedsWrapping = false; + + PromiseCombinatorElements() = default; + + void trace(JSTracer* trc); }; -static PromiseAllDataHolder* -NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue valuesArray, - HandleObject resolve) +void PromiseCombinatorElements::trace(JSTracer* trc) { - PromiseAllDataHolder* dataHolder = NewObjectWithClassProto<PromiseAllDataHolder>(cx); + TraceRoot(trc, &value, "PromiseCombinatorElements::value"); + if (unwrappedArray) { + TraceRoot(trc, &unwrappedArray, + "PromiseCombinatorElements::unwrappedArray"); + } +} + +namespace js { + +template <typename Wrapper> +class WrappedPtrOperations<PromiseCombinatorElements, Wrapper> +{ + const PromiseCombinatorElements& elements() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + HandleValue value() const { + return HandleValue::fromMarkedLocation(&elements().value); + } + + HandleArrayObject unwrappedArray() const { + return HandleArrayObject::fromMarkedLocation(&elements().unwrappedArray); + } +}; + +template <typename Wrapper> +class MutableWrappedPtrOperations<PromiseCombinatorElements, Wrapper> + : public WrappedPtrOperations<PromiseCombinatorElements, Wrapper> +{ + PromiseCombinatorElements& elements() { + return static_cast<Wrapper*>(this)->get(); + } + + public: + MutableHandleValue value() { + return MutableHandleValue::fromMarkedLocation(&elements().value); + } + + MutableHandle<ArrayObject*> unwrappedArray() { + return MutableHandle<ArrayObject*>::fromMarkedLocation( + &elements().unwrappedArray); + } + + void initialize(ArrayObject* arrayObj) { + unwrappedArray().set(arrayObj); + value().setObject(*arrayObj); + + // |needsWrapping| isn't tracked here, because all modifications on the + // initial elements don't require any wrapping. + } + + void initialize(PromiseCombinatorDataHolder* data, ArrayObject* arrayObj, + bool needsWrapping) { + unwrappedArray().set(arrayObj); + value().set(data->valuesArray()); + elements().setElementNeedsWrapping = needsWrapping; + } + + MOZ_MUST_USE bool pushUndefined(JSContext* cx) { + // Helper for the AutoCompartment we need to work with |array|. We mostly do this + // for performance; we could go ahead and do the define via a cross- + // compartment proxy instead... + AutoCompartment ac(cx, unwrappedArray()); + + HandleArrayObject arrayObj = unwrappedArray(); + return js::NewbornArrayPush(cx, arrayObj, UndefinedValue()); + } + + // `Promise.all` Resolve Element Functions + // Step 9. Set values[index] to x. + // + // `Promise.allSettled` Resolve Element Functions + // `Promise.allSettled` Reject Element Functions + // Step 12. Set values[index] to obj. + // + // `Promise.any` Reject Element Functions + // Step 9. Set errors[index] to x. + // + // These handler functions are always created in the compartment of the + // Promise.all/allSettled/any function, which isn't necessarily the same + // compartment as unwrappedArray as explained in NewPromiseCombinatorElements. + // So before storing |val| we may need to enter unwrappedArray's compartment. + MOZ_MUST_USE bool setElement(JSContext* cx, uint32_t index, HandleValue val) { + // The index is guaranteed to be initialized to `undefined`. + MOZ_ASSERT(unwrappedArray()->getDenseElement(index).isUndefined()); + + if (elements().setElementNeedsWrapping) { + AutoCompartment ac(cx, unwrappedArray()); + + RootedValue rootedVal(cx, val); + if (!cx->compartment()->wrap(cx, &rootedVal)) { + return false; + } + unwrappedArray()->setDenseElement(index, rootedVal); + } else { + unwrappedArray()->setDenseElement(index, val); + } + return true; + } +}; + +} // namespace js + +PromiseCombinatorDataHolder* +PromiseCombinatorDataHolder::New( + JSContext* cx, HandleObject resultPromise, Handle<PromiseCombinatorElements> elements, + HandleObject resolveOrReject) +{ + auto* dataHolder = NewBuiltinClassInstance<PromiseCombinatorDataHolder>(cx); if (!dataHolder) return nullptr; assertSameCompartment(cx, resultPromise); - assertSameCompartment(cx, valuesArray); - assertSameCompartment(cx, resolve); + assertSameCompartment(cx, elements.value()); + assertSameCompartment(cx, resolveOrReject); - dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise)); - dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(1)); - dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArray); - dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectValue(*resolve)); + dataHolder->setFixedSlot(Slot_Promise, ObjectValue(*resultPromise)); + dataHolder->setFixedSlot(Slot_RemainingElements, Int32Value(1)); + dataHolder->setFixedSlot(Slot_ValuesArray, elements.value()); + dataHolder->setFixedSlot(Slot_ResolveOrRejectFunction, ObjectValue(*resolveOrReject)); return dataHolder; } @@ -2050,7 +2180,7 @@ static MOZ_MUST_USE bool PerformPromiseRace( JSContext* cx, PromiseForOfIterator& iterator, HandleObject C, Handle<PromiseCapability> resultCapability, bool* done); -enum class IterationMode { All, AllSettled, Race }; +enum class CombinatorKind { All, AllSettled, Race }; // ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646 // @@ -2062,8 +2192,9 @@ enum class IterationMode { All, AllSettled, Race }; // https://tc39.github.io/proposal-promise-allSettled/ // // Promise.allSettled ( iterable ) -static MOZ_MUST_USE bool CommonStaticAllRace(JSContext* cx, CallArgs& args, - IterationMode mode) { +static MOZ_MUST_USE bool +CommonPromiseCombinator(JSContext* cx, CallArgs& args, CombinatorKind mode) +{ HandleValue iterable = args.get(0); // Step 2 (reordered). @@ -2071,13 +2202,13 @@ static MOZ_MUST_USE bool CommonStaticAllRace(JSContext* cx, CallArgs& args, if (!CVal.isObject()) { const char* message; switch (mode) { - case IterationMode::All: + case CombinatorKind::All: message = "Receiver of Promise.all call"; break; - case IterationMode::AllSettled: + case CombinatorKind::AllSettled: message = "Receiver of Promise.allSettled call"; break; - case IterationMode::Race: + case CombinatorKind::Race: message = "Receiver of Promise.race call"; break; } @@ -2102,13 +2233,13 @@ static MOZ_MUST_USE bool CommonStaticAllRace(JSContext* cx, CallArgs& args, if (!iter.valueIsIterable()) { const char* message; switch (mode) { - case IterationMode::All: + case CombinatorKind::All: message = "Argument of Promise.all"; break; - case IterationMode::AllSettled: + case CombinatorKind::AllSettled: message = "Argument of Promise.allSettled"; break; - case IterationMode::Race: + case CombinatorKind::Race: message = "Argument of Promise.race"; break; } @@ -2122,13 +2253,13 @@ static MOZ_MUST_USE bool CommonStaticAllRace(JSContext* cx, CallArgs& args, // Step 7. bool done, result; switch (mode) { - case IterationMode::All: + case CombinatorKind::All: result = PerformPromiseAll(cx, iter, C, promiseCapability, &done); break; - case IterationMode::AllSettled: + case CombinatorKind::AllSettled: result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done); break; - case IterationMode::Race: + case CombinatorKind::Race: result = PerformPromiseRace(cx, iter, C, promiseCapability, &done); break; } @@ -2152,7 +2283,7 @@ static MOZ_MUST_USE bool CommonStaticAllRace(JSContext* cx, CallArgs& args, // 25.6.4.1 Promise.all ( iterable ) static bool Promise_static_all(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - return CommonStaticAllRace(cx, args, IterationMode::All); + return CommonPromiseCombinator(cx, args, CombinatorKind::All); } static MOZ_MUST_USE bool @@ -2164,6 +2295,10 @@ PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle<PromiseObject*> pr Handle<PromiseObject*> promiseToResolve, Handle<PromiseCapability> resultCapability); +static JSFunction* NewPromiseCombinatorElementFunction( + JSContext* cx, Native native, + Handle<PromiseCombinatorDataHolder*> dataHolder, uint32_t index); + static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); // Unforgeable version of ES2016, 25.4.4.1. @@ -2199,20 +2334,25 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Sub-steps 1-2 (omitted). // Sub-step 3. - RootedNativeObject valuesArray(cx, NewDenseFullyAllocatedArray(cx, promiseCount)); - if (!valuesArray) + Rooted<PromiseCombinatorElements> values(cx); + { + auto* valuesArray = NewDenseFullyAllocatedArray(cx, promiseCount); + if (!valuesArray) { return nullptr; - valuesArray->ensureDenseInitializedLength(cx, 0, promiseCount); + } + valuesArray->ensureDenseInitializedLength(cx, 0, promiseCount); + + values.initialize(valuesArray); + } // Sub-step 4. // Create our data holder that holds all the things shared across // every step of the iterator. In particular, this holds the // remainingElementsCount (as an integer reserved slot), the array of // values, and the resolve function from our PromiseCapability. - RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); - Rooted<PromiseAllDataHolder*> dataHolder(cx); - dataHolder = NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, - resultCapability.resolve()); + Rooted<PromiseCombinatorDataHolder*> dataHolder(cx); + dataHolder = PromiseCombinatorDataHolder::New(cx, resultCapability.promise(), + values, resultCapability.resolve()); if (!dataHolder) return nullptr; @@ -2229,25 +2369,17 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Steps e-g (omitted). // Step h. - valuesArray->setDenseElement(index, UndefinedHandleValue); + values.unwrappedArray()->setDenseElement(index, UndefinedHandleValue); // Step i, vastly simplified. RootedObject nextPromiseObj(cx, promises[index]); - // Step j. - RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction, - 1, nullptr, - gc::AllocKind::FUNCTION_EXTENDED, - GenericObject)); + // Steps j-o. + JSFunction* resolveFunc = NewPromiseCombinatorElementFunction( + cx, PromiseAllResolveElementFunction, dataHolder, index); if (!resolveFunc) return nullptr; - // Steps k-o. - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, - ObjectValue(*dataHolder)); - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, - Int32Value(index)); - // Step p. dataHolder->increaseRemainingCount(); @@ -2279,8 +2411,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Sub-step d.iii-iv. if (remainingCount == 0) { - RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); - if (!ResolvePromiseInternal(cx, resultCapability.promise(), valuesArrayVal)) + if (!ResolvePromiseInternal(cx, resultCapability.promise(), values.value())) return nullptr; } } @@ -2356,9 +2487,9 @@ IsPromiseSpecies(JSContext* cx, JSFunction* species); // Runtime Semantics: PerformPromiseAllSettled, step 6. template <typename T> static MOZ_MUST_USE bool -CommonPerformPromiseAllRace(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, - HandleObject resultPromise, bool* done, bool resolveReturnsUndefined, - T getResolveAndReject) +CommonPerformPromiseCombinator(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, + HandleObject resultPromise, bool* done, bool resolveReturnsUndefined, + T getResolveAndReject) { RootedObject promiseCtor(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global())); if (!promiseCtor) @@ -2606,6 +2737,143 @@ CommonPerformPromiseAllRace(JSContext *cx, PromiseForOfIterator& iterator, Handl } } +// Create the elements for the Promise combinators Promise.all and +// Promise.allSettled. +static MOZ_MUST_USE bool +NewPromiseCombinatorElements(JSContext* cx, Handle<PromiseCapability> resultCapability, + MutableHandle<PromiseCombinatorElements> elements) +{ + // We have to be very careful about which compartments we create things for + // the Promise combinators. In particular, we have to maintain the invariant + // that anything stored in a reserved slot is same-compartment with the object + // whose reserved slot it's in. But we want to create the values array in the + // compartment of the result capability's Promise, because that array can get + // exposed as the Promise's resolution value to code that has access to the + // Promise (in particular code from that compartment), and that should work, + // even if the Promise compartment is less-privileged than our caller + // compartment. + // + // So the plan is as follows: Create the values array in the promise + // compartment. Create the promise resolving functions and the data holder in + // our current compartment, i.e. the compartment of the Promise combinator + // function. Store a cross-compartment wrapper to the values array in the + // holder. This should be OK because the only things we hand the promise + // resolving functions to are the "then" calls we do and in the case when the + // Promise's compartment is not the current compartment those are happening + // over Xrays anyway, which means they get the canonical "then" function and + // content can't see our promise resolving functions. + + if (IsWrapper(resultCapability.promise())) { + JSObject* unwrappedPromiseObj = CheckedUnwrap(resultCapability.promise()); + MOZ_ASSERT(unwrappedPromiseObj); + + { + AutoCompartment ac(cx, unwrappedPromiseObj); + auto* array = NewDenseEmptyArray(cx); + if (!array) { + return false; + } + elements.initialize(array); + } + + if (!cx->compartment()->wrap(cx, elements.value())) { + return false; + } + } else { + auto* array = NewDenseEmptyArray(cx); + if (!array) { + return false; + } + + elements.initialize(array); + } + return true; +} + +// Retrieve the combinator elements from the data holder. +static MOZ_MUST_USE bool +GetPromiseCombinatorElements(JSContext* cx, Handle<PromiseCombinatorDataHolder*> data, + MutableHandle<PromiseCombinatorElements> elements) +{ + bool needsWrapping = false; + JSObject* valuesObj = &data->valuesArray().toObject(); + if (IsProxy(valuesObj)) { + // See comment for NewPromiseCombinatorElements for why we unwrap here. + valuesObj = UncheckedUnwrap(valuesObj); + + if (JS_IsDeadWrapper(valuesObj)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_DEAD_OBJECT); + return false; + } + + needsWrapping = true; + } + + elements.initialize(data, &valuesObj->as<ArrayObject>(), needsWrapping); + return true; +} + +static JSFunction* +NewPromiseCombinatorElementFunction(JSContext* cx, Native native, + Handle<PromiseCombinatorDataHolder*> dataHolder, + uint32_t index) +{ + JSFunction* fn = NewNativeFunction(cx, native, 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject); + if (!fn) { + return nullptr; + } + + fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_Data, + ObjectValue(*dataHolder)); + fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex, + Int32Value(index)); + return fn; +} + +// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624 +// 25.6.4.1.2 Promise.all Resolve Element Functions +// 25.6.4.2.2 Promise.allSettled Resolve Element Functions +// 25.6.4.2.3 Promise.allSettled Reject Element Functions +// +// Common implementation for Promise combinator element functions to check if +// they've already been called. +static bool +PromiseCombinatorElementFunctionAlreadyCalled(const CallArgs& args, + MutableHandle<PromiseCombinatorDataHolder*> data, uint32_t* index) +{ + // Step 1. + JSFunction* fn = &args.callee().as<JSFunction>(); + + // Step 2. + const Value& dataVal = + fn->getExtendedSlot(PromiseCombinatorElementFunctionSlot_Data); + + // Step 3. + // We use the existence of the data holder as a signal for whether the Promise + // combinator element function was already called. Upon resolution, it's reset + // to `undefined`. + if (dataVal.isUndefined()) { + return true; + } + + data.set(&dataVal.toObject().as<PromiseCombinatorDataHolder>()); + + // Step 4. + fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_Data, + UndefinedValue()); + + // Step 5. + int32_t idx = + fn->getExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex) + .toInt32(); + MOZ_ASSERT(idx >= 0); + *index = uint32_t(idx); + + return false; +} + // ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646 // 25.6.4.1.1 PerformPromiseAll (iteratorRecord, constructor, resultCapability) static MOZ_MUST_USE bool @@ -2620,47 +2888,9 @@ PerformPromiseAll(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, // Step 2 (omitted). // Step 3. - // We have to be very careful about which compartments we create things in - // here. In particular, we have to maintain the invariant that anything - // stored in a reserved slot is same-compartment with the object whose - // reserved slot it's in. But we want to create the values array in the - // Promise's compartment, because that array can get exposed to - // code that has access to the Promise (in particular code from - // that compartment), and that should work, even if the Promise - // compartment is less-privileged than our caller compartment. - // - // So the plan is as follows: Create the values array in the promise - // compartment. Create the PromiseAllResolveElement function - // and the data holder in our current compartment. Store a - // cross-compartment wrapper to the values array in the holder. This - // should be OK because the only things we hand the - // PromiseAllResolveElement function to are the "then" calls we do and in - // the case when the Promise's compartment is not the current compartment - // those are happening over Xrays anyway, which means they get the - // canonical "then" function and content can't see our - // PromiseAllResolveElement. - RootedArrayObject valuesArray(cx); - RootedValue valuesArrayVal(cx); - if (IsWrapper(resultCapability.promise())) { - JSObject* unwrappedPromiseObj = CheckedUnwrap(resultCapability.promise()); - MOZ_ASSERT(unwrappedPromiseObj); - - { - AutoCompartment ac(cx, unwrappedPromiseObj); - valuesArray = NewDenseEmptyArray(cx); - if (!valuesArray) - return false; - } - - valuesArrayVal.setObject(*valuesArray); - if (!cx->compartment()->wrap(cx, &valuesArrayVal)) - return false; - } else { - valuesArray = NewDenseEmptyArray(cx); - if (!valuesArray) - return false; - - valuesArrayVal.setObject(*valuesArray); + Rooted<PromiseCombinatorElements> values(cx); + if (!NewPromiseCombinatorElements(cx, resultCapability, &values)) { + return false; } // Step 4. @@ -2668,47 +2898,33 @@ PerformPromiseAll(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, // every step of the iterator. In particular, this holds the // remainingElementsCount (as an integer reserved slot), the array of // values, and the resolve function from our PromiseCapability. - Rooted<PromiseAllDataHolder*> dataHolder(cx); - dataHolder = NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, - resultCapability.resolve()); + Rooted<PromiseCombinatorDataHolder*> dataHolder(cx); + dataHolder = PromiseCombinatorDataHolder::New(cx, resultCapability.promise(), + values, resultCapability.resolve()); if (!dataHolder) return false; // Step 5. uint32_t index = 0; - auto getResolveAndReject = [cx, &resultCapability, &valuesArray, &dataHolder, + auto getResolveAndReject = [cx, &resultCapability, &values, &dataHolder, &index](MutableHandleValue resolveFunVal, MutableHandleValue rejectFunVal) { - // Step 6.h. - { // Scope for the AutoCompartment we need to work with valuesArray. We - // mostly do this for performance; we could go ahead and do the define via - // a cross-compartment proxy instead... - AutoCompartment ac(cx, valuesArray); - - if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) - return false; + // Step 8.h. + if (!values.pushUndefined(cx)) { + return false; } - // Steps 6.j-k. - JSFunction* resolveFunc = NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, - nullptr,gc::AllocKind::FUNCTION_EXTENDED, - GenericObject); + // Steps 8.j-p. + JSFunction* resolveFunc = NewPromiseCombinatorElementFunction(cx, + PromiseAllResolveElementFunction, dataHolder, index); if (!resolveFunc) return false; - // Steps 6.l, 6.n-p. - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, - ObjectValue(*dataHolder)); - - // Step 6.m. - resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, - Int32Value(index)); - - // Step 6.q. + // Step 8.q. dataHolder->increaseRemainingCount(); - // Step 6.s. + // Step 8.s. index++; MOZ_ASSERT(index > 0); @@ -2717,95 +2933,70 @@ PerformPromiseAll(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, return true; }; - // Step 6. - if (!CommonPerformPromiseAllRace(cx, iterator, C, resultCapability.promise(), - done, true, getResolveAndReject)) + // Step 8. + if (!CommonPerformPromiseCombinator(cx, iterator, C, resultCapability.promise(), + done, true, getResolveAndReject)) return false; - // Step 6.d.ii. + // Step 8.d.ii. int32_t remainingCount = dataHolder->decreaseRemainingCount(); - // Steps 6.d.iii-iv. + // Steps 8.d.iii-iv. if (remainingCount == 0) { - return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, ResolveMode, + return RunResolutionFunction(cx, resultCapability.resolve(), values.value(), ResolveMode, resultCapability.promise()); } return true; } -// ES2016, 25.4.4.1.2. +// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624 +// 25.6.4.1.2 Promise.all Resolve Element Functions static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - JSFunction* resolve = &args.callee().as<JSFunction>(); - RootedValue xVal(cx, args.get(0)); - - // Step 1. - const Value& dataVal = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data); + HandleValue xVal = args.get(0); - // Step 2. - // We use the existence of the data holder as a signal for whether the - // Promise was already resolved. Upon resolution, it's reset to - // `undefined`. - if (dataVal.isUndefined()) { + // Steps 1-5. + Rooted<PromiseCombinatorDataHolder*> data(cx); + uint32_t index; + if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) { args.rval().setUndefined(); return true; } - Rooted<PromiseAllDataHolder*> data(cx, &dataVal.toObject().as<PromiseAllDataHolder>()); - - // Step 3. - resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); - - // Step 4. - int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) - .toInt32(); - - // Step 5. - RootedValue valuesVal(cx, data->valuesArray()); - RootedObject valuesObj(cx, &valuesVal.toObject()); - if (IsProxy(valuesObj)) { - // See comment for PerformPromiseAll, step 3 for why we unwrap here. - valuesObj = UncheckedUnwrap(valuesObj); - - if (JS_IsDeadWrapper(valuesObj)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); - return false; - } - - AutoCompartment ac(cx, valuesObj); - if (!cx->compartment()->wrap(cx, &xVal)) - return false; + // Step 6. + Rooted<PromiseCombinatorElements> values(cx); + if (!GetPromiseCombinatorElements(cx, data, &values)) { + return false; } - HandleNativeObject values = valuesObj.as<NativeObject>(); - // Step 6 (moved under step 10). - // Step 7 (moved to step 9). + // Step 7 (moved under step 11). + // Step 8 (moved to step 10). - // Step 8. - // The index is guaranteed to be initialized to `undefined`. - MOZ_ASSERT(values->getDenseElement(index).isUndefined()); - values->setDenseElement(index, xVal); + // Step 9. + if (!values.setElement(cx, index, xVal)) { + return false; + } - // Steps 7,9. + // Steps 8,10. uint32_t remainingCount = data->decreaseRemainingCount(); - // Step 10. + // Step 11. if (remainingCount == 0) { - // Step 10.a. (Omitted, happened in PerformPromiseAll.) - // Step 10.b. + // Step 11.a. (Omitted, happened in PerformPromiseAll.) + // Step 11.b. - // Step 6 (Adapted to work with PromiseAllDataHolder's layout). - RootedObject resolveAllFun(cx, data->resolveObj()); + // Step 7 (Adapted to work with PromiseCombinatorDataHolder's layout). + RootedObject resolveAllFun(cx, data->resolveOrRejectObj()); RootedObject promiseObj(cx, data->promiseObj()); - if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) + if (!RunResolutionFunction(cx, resolveAllFun, values.value(), ResolveMode, promiseObj)) return false; } - // Step 11. + // Step 12. args.rval().setUndefined(); return true; } @@ -2816,7 +3007,7 @@ static bool Promise_static_race(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - return CommonStaticAllRace(cx, args, IterationMode::Race); + return CommonPromiseCombinator(cx, args, CombinatorKind::Race); } // ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646 @@ -2846,8 +3037,8 @@ PerformPromiseRace(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C return true; }; - // Step 3. - return CommonPerformPromiseAllRace(cx, iterator, C, + // Step 3-5. + return CommonPerformPromiseCombinator(cx, iterator, C, resultCapability.promise(), done, isDefaultResolveFn, getResolveAndReject); } @@ -2869,7 +3060,7 @@ static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc, // Promise.allSettled ( iterable ) static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - return CommonStaticAllRace(cx, args, IterationMode::AllSettled); + return CommonPromiseCombinator(cx, args, CombinatorKind::AllSettled); } // Promise.allSettled (Stage 3 proposal) @@ -2887,31 +3078,9 @@ static MOZ_MUST_USE bool PerformPromiseAllSettled( // Step 2 (omitted). // Step 3. - // See the big comment in PerformPromiseAll about which objects should be - // created in which compartments. - RootedArrayObject valuesArray(cx); - RootedValue valuesArrayVal(cx); - if (IsWrapper(resultCapability.promise())) { - JSObject* unwrappedPromiseObj = CheckedUnwrap(resultCapability.promise()); - MOZ_ASSERT(unwrappedPromiseObj); - - { - AutoCompartment ac(cx, unwrappedPromiseObj); - valuesArray = NewDenseEmptyArray(cx); - if (!valuesArray) - return false; - } - - valuesArrayVal.setObject(*valuesArray); - if (!cx->compartment()->wrap(cx, &valuesArrayVal)) { - return false; - } - } else { - valuesArray = NewDenseEmptyArray(cx); - if (!valuesArray) - return false; - - valuesArrayVal.setObject(*valuesArray); + Rooted<PromiseCombinatorElements> values(cx); + if (!NewPromiseCombinatorElements(cx, resultCapability, &values)) { + return false; } // Step 4. @@ -2919,10 +3088,10 @@ static MOZ_MUST_USE bool PerformPromiseAllSettled( // of the iterator. In particular, this holds the remainingElementsCount // (as an integer reserved slot), the array of values, and the resolve // function from our PromiseCapability. - Rooted<PromiseAllDataHolder*> dataHolder(cx); - dataHolder = - NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, - resultCapability.resolve()); + Rooted<PromiseCombinatorDataHolder*> dataHolder(cx); + dataHolder = PromiseCombinatorDataHolder::New(cx, resultCapability.promise(), + values, + resultCapability.resolve()); if (!dataHolder) { return false; } @@ -2930,18 +3099,12 @@ static MOZ_MUST_USE bool PerformPromiseAllSettled( // Step 5. uint32_t index = 0; - auto getResolveAndReject = [cx, &valuesArray, &dataHolder, &index]( + auto getResolveAndReject = [cx, &values, &dataHolder, &index]( MutableHandleValue resolveFunVal, MutableHandleValue rejectFunVal) { - // Step 6.h. - { // Scope for the AutoCompartment we need to work with valuesArray. We - // mostly do this for performance; we could go ahead and do the define via - // a cross-compartment proxy instead... - AutoCompartment ac(cx, valuesArray); - - if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) { - return false; - } + // Step 8.h. + if (!values.pushUndefined(cx)) { + return false; } auto PromiseAllSettledResolveElementFunction = @@ -2951,40 +3114,22 @@ static MOZ_MUST_USE bool PerformPromiseAllSettled( PromiseAllSettledElementFunction< PromiseAllSettledElementFunctionKind::Reject>; - // Steps 6.j-m. - JSFunction* resolveFunc = NewNativeFunction( - cx, PromiseAllSettledResolveElementFunction, 1, nullptr, - gc::AllocKind::FUNCTION_EXTENDED, GenericObject); + // Steps 8.j-q. + JSFunction* resolveFunc = NewPromiseCombinatorElementFunction( + cx, PromiseAllSettledResolveElementFunction, dataHolder, index); if (!resolveFunc) { return false; } resolveFunVal.setObject(*resolveFunc); - // Steps 6.o-q. - resolveFunc->setExtendedSlot(PromiseAllSettledElementFunctionSlot_Data, - ObjectValue(*dataHolder)); - - // Step 6.n. - resolveFunc->setExtendedSlot( - PromiseAllSettledElementFunctionSlot_ElementIndex, Int32Value(index)); - - // Steps 6.r-t. - JSFunction* rejectFunc = NewNativeFunction( - cx, PromiseAllSettledRejectElementFunction, 1, nullptr, - gc::AllocKind::FUNCTION_EXTENDED, GenericObject); + // Steps 8.r-x. + JSFunction* rejectFunc = NewPromiseCombinatorElementFunction( + cx, PromiseAllSettledRejectElementFunction, dataHolder, index); if (!rejectFunc) { return false; } rejectFunVal.setObject(*rejectFunc); - // Steps 6.v-x. - rejectFunc->setExtendedSlot(PromiseAllSettledElementFunctionSlot_Data, - ObjectValue(*dataHolder)); - - // Step 6.u. - rejectFunc->setExtendedSlot( - PromiseAllSettledElementFunctionSlot_ElementIndex, Int32Value(index)); - // Step 6.y. dataHolder->increaseRemainingCount(); @@ -2995,72 +3140,54 @@ static MOZ_MUST_USE bool PerformPromiseAllSettled( return true; }; - // Step 6. - if (!CommonPerformPromiseAllRace(cx, iterator, C, resultCapability.promise(), - done, true, getResolveAndReject)) { + // Step 5-6 and 8. + if (!CommonPerformPromiseCombinator(cx, iterator, C, resultCapability.promise(), + done, true, getResolveAndReject)) { return false; } - // Step 6.d.ii. + // Step 8.d.ii. int32_t remainingCount = dataHolder->decreaseRemainingCount(); - // Steps 6.d.iii-iv. + // Steps 8.d.iii-iv. if (remainingCount == 0) { - return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, + return RunResolutionFunction(cx, resultCapability.resolve(), values.value(), ResolveMode, resultCapability.promise()); } return true; } -// Promise.allSettled (Stage 3 proposal) -// https://tc39.github.io/proposal-promise-allSettled/ -// -// Promise.allSettled Resolve Element Functions -// Promise.allSettled Reject Element Functions +// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624 +// 25.6.4.2.2 Promise.allSettled Resolve Element Functions +// 25.6.4.2.3 Promise.allSettled Reject Element Functions template <PromiseAllSettledElementFunctionKind Kind> static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); HandleValue valueOrReason = args.get(0); - // Step 1. - JSFunction* resolve = &args.callee().as<JSFunction>(); - Rooted<PromiseAllDataHolder*> data( - cx, &resolve->getExtendedSlot(PromiseAllSettledElementFunctionSlot_Data) - .toObject() - .as<PromiseAllDataHolder>()); - - // Steps 2-4 (moved below). - - // Step 5. - int32_t index = - resolve - ->getExtendedSlot(PromiseAllSettledElementFunctionSlot_ElementIndex) - .toInt32(); + // Steps 1-5. + Rooted<PromiseCombinatorDataHolder*> data(cx); + uint32_t index; + if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) { + args.rval().setUndefined(); + return true; + } // Step 6. - RootedValue valuesVal(cx, data->valuesArray()); - RootedObject valuesObj(cx, &valuesVal.toObject()); - bool needsWrapping = false; - if (IsProxy(valuesObj)) { - // See comment for PerformPromiseAllSettled, step 3 for why we unwrap here. - valuesObj = UncheckedUnwrap(valuesObj); - - if (JS_IsDeadWrapper(valuesObj)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_DEAD_OBJECT); - return false; - } - - needsWrapping = true; + Rooted<PromiseCombinatorElements> values(cx); + if (!GetPromiseCombinatorElements(cx, data, &values)) { + return false; } - HandleNativeObject values = valuesObj.as<NativeObject>(); // Steps 2-3. + // The already-called check above only handles the case when |this| function + // is called repeatedly, so we still need to check if the other pair of this + // resolving function was already called: // We use the element value as a signal for whether the Promise was already // fulfilled. Upon resolution, it's set to the result object created below. - if (!values->getDenseElement(index).isUndefined()) { + if (!values.unwrappedArray()->getDenseElement(index).isUndefined()) { args.rval().setUndefined(); return true; } @@ -3095,17 +3222,12 @@ static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc, return false; } + // Steps 4, 12. RootedValue objVal(cx, ObjectValue(*obj)); - if (needsWrapping) { - AutoCompartment ac(cx, valuesObj); - if (!cx->compartment()->wrap(cx, &objVal)) { - return false; - } + if (!values.setElement(cx, index, objVal)) { + return false; } - // Steps 4, 12. - values->setDenseElement(index, objVal); - // Steps 8, 13. uint32_t remainingCount = data->decreaseRemainingCount(); @@ -3114,10 +3236,10 @@ static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc, // Step 14.a. (Omitted, happened in PerformPromiseAllSettled.) // Step 14.b. - // Step 7 (Adapted to work with PromiseAllDataHolder's layout). - RootedObject resolveAllFun(cx, data->resolveObj()); + // Step 7 (Adapted to work with PromiseCombinatorDataHolder's layout). + RootedObject resolveAllFun(cx, data->resolveOrRejectObj()); RootedObject promiseObj(cx, data->promiseObj()); - if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, + if (!RunResolutionFunction(cx, resolveAllFun, values.value(), ResolveMode, promiseObj)) { return false; } |