summaryrefslogtreecommitdiff
path: root/js/src/builtin/Promise.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/Promise.cpp')
-rw-r--r--js/src/builtin/Promise.cpp758
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;
}