diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 75024c8a7e..c29d6380e6 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -127,6 +127,67 @@ enum PromiseAllDataHolderSlots { PromiseAllDataHolderSlots, }; +struct PromiseCapability { + JSObject* promise = nullptr; + JSObject* resolve = nullptr; + JSObject* reject = nullptr; + + PromiseCapability() = default; + + static void trace(PromiseCapability* self, JSTracer* trc) { self->trace(trc); } + void trace(JSTracer* trc); +}; + +void +PromiseCapability::trace(JSTracer* trc) +{ + if (promise) + TraceRoot(trc, &promise, "PromiseCapability::promise"); + if (resolve) + TraceRoot(trc, &resolve, "PromiseCapability::resolve"); + if (reject) + TraceRoot(trc, &reject, "PromiseCapability::reject"); +} + +namespace js { + +template +class WrappedPtrOperations +{ + const PromiseCapability& capability() const { return static_cast(this)->get(); } + + public: + HandleObject promise() const { + return HandleObject::fromMarkedLocation(&capability().promise); + } + HandleObject resolve() const { + return HandleObject::fromMarkedLocation(&capability().resolve); + } + HandleObject reject() const { + return HandleObject::fromMarkedLocation(&capability().reject); + } +}; + +template +class MutableWrappedPtrOperations + : public WrappedPtrOperations +{ + PromiseCapability& capability() { return static_cast(this)->get(); } + + public: + MutableHandleObject promise() { + return MutableHandleObject::fromMarkedLocation(&capability().promise); + } + MutableHandleObject resolve() { + return MutableHandleObject::fromMarkedLocation(&capability().resolve); + } + MutableHandleObject reject() { + return MutableHandleObject::fromMarkedLocation(&capability().reject); + } +}; + +} // namespace js + class PromiseAllDataHolder : public NativeObject { public: @@ -366,7 +427,7 @@ static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resol // be tedious, so the check in step 1 and the entirety of step 2 aren't // included. static bool -AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, HandleObject reject) +AbruptRejectPromise(JSContext* cx, CallArgs& args, HandleObject promiseObj, HandleObject reject) { // Step 1.a. RootedValue reason(cx); @@ -381,6 +442,12 @@ AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, Hand return true; } +static bool +AbruptRejectPromise(JSContext* cx, CallArgs& args, Handle capability) +{ + return AbruptRejectPromise(cx, args, capability.promise(), capability.reject()); +} + enum ReactionRecordSlots { // The promise for which this record provides a reaction handler. // Matches the [[Capability]].[[Promise]] field from the spec. @@ -1039,8 +1106,7 @@ CreatePromiseWithDefaultResolutionFunctions(JSContext* cx, MutableHandleObject r // ES2016, 25.4.1.5. static MOZ_MUST_USE bool -NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, - MutableHandleObject resolve, MutableHandleObject reject, +NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandle capability, bool canOmitResolutionFunctions) { RootedValue cVal(cx, ObjectValue(*C)); @@ -1065,12 +1131,17 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, // of the GetCapabilitiesExecutor function, and directly allocate the // result promise instead of invoking the Promise constructor. if (IsNativeFunction(cVal, PromiseConstructor)) { - if (canOmitResolutionFunctions) - promise.set(CreatePromiseObjectWithoutResolutionFunctions(cx)); - else - promise.set(CreatePromiseWithDefaultResolutionFunctions(cx, resolve, reject)); + PromiseObject* promise; + if (canOmitResolutionFunctions) { + promise = CreatePromiseObjectWithoutResolutionFunctions(cx); + } else { + promise = CreatePromiseWithDefaultResolutionFunctions(cx, capability.resolve(), + capability.reject()); + } if (!promise) return false; + + capability.promise().set(promise); return true; } @@ -1088,7 +1159,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, // Step 6. FixedConstructArgs<1> cargs(cx); cargs[0].setObject(*executor); - if (!Construct(cx, cVal, cargs, cVal, promise)) + if (!Construct(cx, cVal, cargs, cVal, capability.promise())) return false; // Step 7. @@ -1108,8 +1179,8 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, } // Step 9 (well, the equivalent for all of promiseCapabilities' fields.) - resolve.set(&resolveVal.toObject()); - reject.set(&rejectVal.toObject()); + capability.resolve().set(&resolveVal.toObject()); + capability.reject().set(&rejectVal.toObject()); // Step 10. return true; @@ -1955,10 +2026,9 @@ PromiseObject::createSkippingExecutor(JSContext* cx) return CreatePromiseObjectWithoutResolutionFunctions(cx); } -static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, - HandleObject C, HandleObject promiseObj, - HandleObject resolve, HandleObject reject, - bool* done); +static MOZ_MUST_USE bool +PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done); // ES2016, 25.4.4.1. static bool @@ -1979,28 +2049,26 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) RootedObject C(cx, &CVal.toObject()); // Step 3. - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + Rooted promiseCapability(cx); + if (!NewPromiseCapability(cx, C, &promiseCapability, false)) return false; // Steps 4-5. JS::ForOfIterator iter(cx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, "Argument of Promise.all"); - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } // Step 6 (implicit). // Step 7. bool done; - bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject, &done); + bool result = PerformPromiseAll(cx, iter, C, promiseCapability, &done); // Step 8. if (!result) { @@ -2009,18 +2077,17 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) iter.closeThrow(); // Step 8.b. - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } // Step 9. - args.rval().setObject(*resultPromise); + args.rval().setObject(*promiseCapability.promise()); return true; } -static MOZ_MUST_USE bool PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, - HandleObject C, HandleObject promiseObj, - HandleObject resolve, HandleObject reject, - bool* done); +static MOZ_MUST_USE bool +PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done); // ES2020 static bool @@ -2036,46 +2103,42 @@ Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) } RootedObject C(cx, &CVal.toObject()); - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + Rooted promiseCapability(cx); + if (!NewPromiseCapability(cx, C, &promiseCapability, false)) return false; JS::ForOfIterator iter(cx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, "Argument of Promise.allSettled"); - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } bool done; - bool result = PerformPromiseAllSettled(cx, iter, C, resultPromise, resolve, reject, &done); + bool result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done); if (!result) { if (!done) iter.closeThrow(); - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } - args.rval().setObject(*resultPromise); + args.rval().setObject(*promiseCapability.promise()); return true; } -static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, - HandleValue onFulfilled_, HandleValue onRejected_, - HandleObject resultPromise, - HandleObject resolve, HandleObject reject); +static MOZ_MUST_USE bool +PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, + HandleValue onRejected_, Handle resultCapability); static MOZ_MUST_USE bool PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, Handle promiseToResolve, - HandleObject resultPromise, HandleObject resolve, - HandleObject reject); + Handle resultCapability); static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); static bool PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); @@ -2101,10 +2164,8 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Step 2 (omitted). // Step 3. - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + Rooted resultCapability(cx); + if (!NewPromiseCapability(cx, C, &resultCapability, false)) return nullptr; // Steps 4-6 (omitted). @@ -2127,12 +2188,16 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // remainingElementsCount (as an integer reserved slot), the array of // values, and the resolve function from our PromiseCapability. RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); - Rooted dataHolder(cx, NewPromiseAllDataHolder(cx, resultPromise, - valuesArrayVal, - resolve)); + Rooted dataHolder(cx); + dataHolder = NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, + resultCapability.resolve()); if (!dataHolder) return nullptr; + // Call PerformPromiseThen with resolve and reject set to nullptr. + Rooted resultCapabilityWithoutResolving(cx); + resultCapabilityWithoutResolving.promise().set(resultCapability.promise()); + // Sub-step 5 (inline in loop-header below). // Sub-step 6. @@ -2166,7 +2231,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Step q, very roughly. RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); - RootedValue rejectFunVal(cx, ObjectValue(*reject)); + RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); Rooted nextPromise(cx); // GetWaitForAllPromise is used internally only and must not @@ -2178,7 +2243,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) nextPromise = &UncheckedUnwrap(nextPromiseObj)->as(); if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal, - resultPromise, nullptr, nullptr)) + resultCapabilityWithoutResolving)) { return nullptr; } @@ -2193,7 +2258,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, resultPromise, valuesArrayVal)) + if (!ResolvePromiseInternal(cx, resultCapability.promise(), valuesArrayVal)) return nullptr; } } @@ -2201,7 +2266,7 @@ js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) // Step 8 (omitted). // Step 9. - return resultPromise; + return resultCapability.promise(); } static MOZ_MUST_USE bool @@ -2241,11 +2306,12 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res // ES2016, 25.4.4.1.1. static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - HandleObject promiseObj, HandleObject resolve, HandleObject reject, - bool* done) + Handle resultCapability, bool* done) { *done = false; + HandleObject promiseObj = resultCapability.promise(); + // Step 1. MOZ_ASSERT(C->isConstructor()); RootedValue CVal(cx, ObjectValue(*C)); @@ -2272,30 +2338,38 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // those are happening over Xrays anyway, which means they get the // canonical "then" function and content can't see our // PromiseAllResolveElement. - RootedObject valuesArray(cx); + RootedArrayObject valuesArray(cx); + RootedValue valuesArrayVal(cx); if (IsWrapper(promiseObj)) { JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); MOZ_ASSERT(unwrappedPromiseObj); - AutoCompartment ac(cx, unwrappedPromiseObj); - valuesArray = NewDenseFullyAllocatedArray(cx, 0); - } else { - valuesArray = NewDenseFullyAllocatedArray(cx, 0); - } - if (!valuesArray) - return false; + { + AutoCompartment ac(cx, unwrappedPromiseObj); + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + } - RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); - if (!cx->compartment()->wrap(cx, &valuesArrayVal)) - return false; + valuesArrayVal.setObject(*valuesArray); + if (!cx->compartment()->wrap(cx, &valuesArrayVal)) + return false; + } else { + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + + valuesArrayVal.setObject(*valuesArray); + } // 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. - Rooted dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj, - valuesArrayVal, resolve)); + Rooted dataHolder(cx); + dataHolder = NewPromiseAllDataHolder(cx, promiseObj, valuesArrayVal, + resultCapability.resolve()); if (!dataHolder) return false; @@ -2305,8 +2379,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Step 6. RootedValue nextValue(cx); RootedValue nextPromise(cx); - RootedId indexId(cx); - RootedValue rejectFunVal(cx, ObjectValue(*reject)); + RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); RootedValue resolveFunVal(cx); RootedValue staticResolve(cx); @@ -2329,8 +2402,8 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Steps d.iii-iv. if (remainingCount == 0) { - return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, - promiseObj); + return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, + ResolveMode, promiseObj); } // We're all set for now! @@ -2342,8 +2415,8 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // mostly do this for performance; we could go ahead and do the define via // a cross-compartment proxy instead... AutoCompartment ac(cx, valuesArray); - indexId = INT_TO_JSID(index); - if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) + + if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) return false; } @@ -2389,35 +2462,41 @@ 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) + Handle resultCapability, bool* done) { *done = false; - RootedObject unwrappedPromiseObj(cx); - if (IsWrapper(promiseObj)) { - unwrappedPromiseObj = CheckedUnwrap(promiseObj); - MOZ_ASSERT(unwrappedPromiseObj); - } + HandleObject promiseObj = resultCapability.promise(); RootedValue CVal(cx, ObjectValue(*C)); - RootedObject valuesArray(cx); - if (unwrappedPromiseObj) { - JSAutoCompartment ac(cx, unwrappedPromiseObj); - valuesArray = NewDenseFullyAllocatedArray(cx, 0); + RootedArrayObject valuesArray(cx); + RootedValue valuesArrayVal(cx); + if (IsWrapper(promiseObj)) { + JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); + 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 = NewDenseFullyAllocatedArray(cx, 0); + valuesArray = NewDenseEmptyArray(cx); + if (!valuesArray) + return false; + + valuesArrayVal.setObject(*valuesArray); } - 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)); + Rooted dataHolder(cx); + dataHolder = NewPromiseAllDataHolder(cx, promiseObj, valuesArrayVal, + resultCapability.resolve()); if (!dataHolder) return false; RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); @@ -2425,7 +2504,6 @@ PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObjec uint32_t index = 0; RootedValue nextValue(cx); - RootedId indexId(cx); while (true) { if (!iterator.next(&nextValue, done)) { @@ -2436,16 +2514,16 @@ PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObjec if (*done) { int32_t remainingCount = dataHolder->decreaseRemainingCount(); if (remainingCount == 0) { - return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, - promiseObj); + return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, + ResolveMode, promiseObj); } return true; } { // Scoped for AutoCompartment JSAutoCompartment ac(cx, valuesArray); - indexId = INT_TO_JSID(index); - if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) + + if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) return false; } @@ -2736,10 +2814,9 @@ PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp) return true; } -static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, - HandleObject C, HandleObject promiseObj, - HandleObject resolve, HandleObject reject, - bool* done); +static MOZ_MUST_USE bool +PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + Handle resultCapability, bool* done); // ES2016, 25.4.4.3. static bool @@ -2760,28 +2837,26 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp) RootedObject C(cx, &CVal.toObject()); // Step 3. - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + Rooted promiseCapability(cx); + if (!NewPromiseCapability(cx, C, &promiseCapability, false)) return false; // Steps 4-5. JS::ForOfIterator iter(cx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, "Argument of Promise.race"); - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } // Step 6 (implicit). // Step 7. bool done; - bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject, &done); + bool result = PerformPromiseRace(cx, iter, C, promiseCapability, &done); // Step 8. if (!result) { @@ -2790,19 +2865,18 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp) iter.closeThrow(); // Step 8.b. - return AbruptRejectPromise(cx, args, resultPromise, reject); + return AbruptRejectPromise(cx, args, promiseCapability); } // Step 9. - args.rval().setObject(*resultPromise); + args.rval().setObject(*promiseCapability.promise()); return true; } // ES2016, 25.4.4.3.1. static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, - HandleObject promiseObj, HandleObject resolve, HandleObject reject, - bool* done) + Handle resultCapability, bool* done) { *done = false; MOZ_ASSERT(C->isConstructor()); @@ -2811,11 +2885,16 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // BlockOnPromise fast path requires the passed onFulfilled function // doesn't return an object value, because otherwise the skipped promise // creation is detectable due to missing property lookups. - bool isDefaultResolveFn = IsNativeFunction(resolve, ResolvePromiseFunction); + bool isDefaultResolveFn = IsNativeFunction(resultCapability.resolve(), + ResolvePromiseFunction); + + HandleObject promiseObj = resultCapability.promise(); + RootedValue resolveFunVal(cx, ObjectValue(*resultCapability.resolve())); + RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); RootedValue nextValue(cx); - RootedValue resolveFunVal(cx, ObjectValue(*resolve)); - RootedValue rejectFunVal(cx, ObjectValue(*reject)); + RootedValue nextPromise(cx); + RootedValue staticResolve(cx); while (true) { // Steps a-c, e-g. @@ -2838,8 +2917,6 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Step h. // Sadly, because someone could have overridden // "resolve" on the canonical Promise constructor. - RootedValue nextPromise(cx); - RootedValue staticResolve(cx); if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) return false; @@ -2902,21 +2979,19 @@ CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue ar } // Step 4 of Resolve, 3 of Reject. - RootedObject promise(cx); - RootedObject resolveFun(cx); - RootedObject rejectFun(cx); - if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun, true)) + Rooted capability(cx); + if (!NewPromiseCapability(cx, C, &capability, true)) return nullptr; // Step 5 of Resolve, 4 of Reject. - if (!RunResolutionFunction(cx, mode == ResolveMode ? resolveFun : rejectFun, argVal, mode, - promise)) + if (!RunResolutionFunction(cx, mode == ResolveMode ? capability.resolve() : capability.reject(), + argVal, mode, capability.promise())) { return nullptr; } // Step 6 of Resolve, 4 of Reject. - return promise; + return capability.promise(); } MOZ_MUST_USE JSObject* @@ -3004,18 +3079,22 @@ enum class IncumbentGlobalObject { }; static PromiseReactionRecord* -NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfilled, - HandleValue onRejected, HandleObject resolve, HandleObject reject, +NewReactionRecord(JSContext* cx, Handle resultCapability, + HandleValue onFulfilled, HandleValue onRejected, IncumbentGlobalObject incumbentGlobalObjectOption) { // Either of the following conditions must be met: - // * resultPromise is a PromiseObject - // * resolve and reject are callable + // * resultCapability.promise is a PromiseObject + // * resultCapability.resolve and resultCapability.resolve are callable // except for Async Generator, there resultPromise can be nullptr. - MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), resolve); - MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), IsCallable(resolve)); - MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), reject); - MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), IsCallable(reject)); +#ifdef DEBUG + if (resultCapability.promise() && !resultCapability.promise()->is()) { + MOZ_ASSERT(resultCapability.resolve()); + MOZ_ASSERT(IsCallable(resultCapability.resolve())); + MOZ_ASSERT(resultCapability.reject()); + MOZ_ASSERT(IsCallable(resultCapability.reject())); + } +#endif // Ensure the onFulfilled handler has the expected type. MOZ_ASSERT(onFulfilled.isInt32() || onFulfilled.isObjectOrNull()); @@ -3042,19 +3121,22 @@ NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfi if (!reaction) return nullptr; - assertSameCompartment(cx, resultPromise); + assertSameCompartment(cx, resultCapability.promise()); assertSameCompartment(cx, onFulfilled); assertSameCompartment(cx, onRejected); - assertSameCompartment(cx, resolve); - assertSameCompartment(cx, reject); + assertSameCompartment(cx, resultCapability.resolve()); + assertSameCompartment(cx, resultCapability.reject()); assertSameCompartment(cx, incumbentGlobalObject); - reaction->setFixedSlot(ReactionRecordSlot_Promise, ObjectOrNullValue(resultPromise)); + reaction->setFixedSlot(ReactionRecordSlot_Promise, + ObjectOrNullValue(resultCapability.promise())); reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(0)); reaction->setFixedSlot(ReactionRecordSlot_OnFulfilled, onFulfilled); reaction->setFixedSlot(ReactionRecordSlot_OnRejected, onRejected); - reaction->setFixedSlot(ReactionRecordSlot_Resolve, ObjectOrNullValue(resolve)); - reaction->setFixedSlot(ReactionRecordSlot_Reject, ObjectOrNullValue(reject)); + reaction->setFixedSlot(ReactionRecordSlot_Resolve, + ObjectOrNullValue(resultCapability.resolve())); + reaction->setFixedSlot(ReactionRecordSlot_Reject, + ObjectOrNullValue(resultCapability.reject())); reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject, ObjectOrNullValue(incumbentGlobalObject)); @@ -3070,8 +3152,7 @@ IsPromiseSpecies(JSContext* cx, JSFunction* species) static bool PromiseThenNewPromiseCapability(JSContext* cx, HandleObject promiseObj, CreateDependentPromise createDependent, - MutableHandleObject resultPromise, - MutableHandleObject resolve, MutableHandleObject reject) + MutableHandle resultCapability) { if (createDependent != CreateDependentPromise::Never) { // Step 3. @@ -3083,7 +3164,7 @@ PromiseThenNewPromiseCapability(JSContext* cx, HandleObject promiseObj, !IsNativeFunction(C, PromiseConstructor)) { // Step 4. - if (!NewPromiseCapability(cx, C, resultPromise, resolve, reject, true)) + if (!NewPromiseCapability(cx, C, resultCapability, true)) return false; } } @@ -3103,21 +3184,16 @@ js::OriginalPromiseThen(JSContext* cx, Handle promise, return false; } - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); - - if (!PromiseThenNewPromiseCapability(cx, promiseObj, createDependent, &resultPromise, - &resolve, &reject)) - { + // Steps 3-4. + Rooted resultCapability(cx); + if (!PromiseThenNewPromiseCapability(cx, promiseObj, createDependent, &resultCapability)) return false; - } // Step 5. - if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject)) + if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultCapability)) return false; - dependent.set(resultPromise); + dependent.set(resultCapability.promise()); return true; } @@ -3128,18 +3204,15 @@ OriginalPromiseThenWithoutSettleHandlers(JSContext* cx, Handle p assertSameCompartment(cx, promise); // Steps 3-4. - RootedObject resultPromise(cx); - RootedObject resolve(cx); - RootedObject reject(cx); + Rooted resultCapability(cx); if (!PromiseThenNewPromiseCapability(cx, promise, CreateDependentPromise::SkipIfCtorUnobservable, - &resultPromise, &resolve, &reject)) + &resultCapability)) { return false; } // Step 5. - return PerformPromiseThenWithoutSettleHandlers(cx, promise, promiseToResolve, resultPromise, - resolve, reject); + return PerformPromiseThenWithoutSettleHandlers(cx, promise, promiseToResolve, resultCapability); } static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, @@ -3218,9 +3291,10 @@ InternalAwait(JSContext* cx, HandleValue value, HandleObject resultPromise, return false; // Steps 7-8. - Rooted reaction(cx, NewReactionRecord(cx, resultPromise, + Rooted resultCapability(cx); + resultCapability.promise().set(resultPromise); + Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, - nullptr, nullptr, IncumbentGlobalObject::Yes)); if (!reaction) return false; @@ -3797,8 +3871,7 @@ Promise_then(JSContext* cx, unsigned argc, Value* vp) // ES2016, 25.4.5.3.1. static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, - HandleValue onRejected_, HandleObject resultPromise, - HandleObject resolve, HandleObject reject) + HandleValue onRejected_, Handle resultCapability) { // Step 1 (implicit). // Step 2 (implicit). @@ -3814,9 +3887,8 @@ PerformPromiseThen(JSContext* cx, Handle promise, HandleValue on onRejected = Int32Value(PromiseHandlerThrower); // Step 7. - Rooted reaction(cx, NewReactionRecord(cx, resultPromise, + Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, - resolve, reject, IncumbentGlobalObject::Yes)); if (!reaction) return false; @@ -3827,8 +3899,7 @@ PerformPromiseThen(JSContext* cx, Handle promise, HandleValue on static MOZ_MUST_USE bool PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, Handle promiseToResolve, - HandleObject resultPromise, HandleObject resolve, - HandleObject reject) + Handle resultCapability) { // Step 1 (implicit). // Step 2 (implicit). @@ -3840,9 +3911,8 @@ PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle pr HandleValue onRejected = NullHandleValue; // Step 7. - Rooted reaction(cx, NewReactionRecord(cx, resultPromise, + Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, - resolve, reject, IncumbentGlobalObject::Yes)); if (!reaction) return false; @@ -3929,9 +3999,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis if (!C) return false; - RootedObject resultPromise(cx); - RootedObject resolveFun(cx); - RootedObject rejectFun(cx); + Rooted resultCapability(cx); // By default, the blocked promise is added as an extra entry to the // rejected promises list. @@ -3951,21 +4019,18 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis !PromiseHasAnyFlag(blockedPromise_->as(), PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) { - resultPromise.set(blockedPromise_); + resultCapability.promise().set(blockedPromise_); addToDependent = false; } else { // 25.4.5.3., step 4. - if (!NewPromiseCapability(cx, C, &resultPromise, &resolveFun, &rejectFun, true)) + if (!NewPromiseCapability(cx, C, &resultCapability, true)) return false; } // 25.4.5.3., step 5. Handle promise = promiseObj.as(); - if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, - resolveFun, rejectFun)) - { + if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultCapability)) return false; - } if (!addToDependent) return true; @@ -4098,9 +4163,12 @@ AddDummyPromiseReactionForDebugger(JSContext* cx, Handle promise if (promise->state() != JS::PromiseState::Pending) return true; - Rooted reaction(cx, NewReactionRecord(cx, dependentPromise, + // Leave resolve and reject as null. + Rooted capability(cx); + capability.promise().set(dependentPromise); + + Rooted reaction(cx, NewReactionRecord(cx, capability, NullHandleValue, NullHandleValue, - nullptr, nullptr, IncumbentGlobalObject::No)); if (!reaction) return false; diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h index 4d692f4a9f..e2fccbdd44 100644 --- a/js/src/vm/NativeObject-inl.h +++ b/js/src/vm/NativeObject-inl.h @@ -64,13 +64,19 @@ NativeObject::clearShouldConvertDoubleElements() } inline void -NativeObject::setDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val) +NativeObject::addDenseElementType(ExclusiveContext* cx, uint32_t index, const Value& val) { // Avoid a slow AddTypePropertyId call if the type is the same as the type // of the previous element. TypeSet::Type thisType = TypeSet::GetValueType(val); if (index == 0 || TypeSet::GetValueType(elements_[index - 1]) != thisType) AddTypePropertyId(cx, this, JSID_VOID, thisType); +} + +inline void +NativeObject::setDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val) +{ + addDenseElementType(cx, index, val); setDenseElementMaybeConvertDouble(index, val); } @@ -78,10 +84,9 @@ inline void NativeObject::initDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val) { MOZ_ASSERT(!shouldConvertDoubleElements()); - if (val.isMagic(JS_ELEMENTS_HOLE)) - markDenseElementsNotPacked(cx); - else - AddTypePropertyId(cx, this, JSID_VOID, val); + MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE)); + + addDenseElementType(cx, index, val); initDenseElement(index, val); } diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 030dcfb888..c5865caa03 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -1076,6 +1076,10 @@ class NativeObject : public ShapedObject setDenseElement(index, val); } + private: + inline void addDenseElementType(ExclusiveContext* cx, uint32_t index, const Value& val); + + public: inline void setDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val); inline void initDenseElementWithType(ExclusiveContext* cx, uint32_t index,