Issue #2089 - Don't use BlockOnPromise fast path with non-default resolving functions, combine default resolving functions flags

Based-on: m-c 1474348
This commit is contained in:
Martok 2023-01-23 00:13:31 +01:00 committed by roytam1
commit ee395231dd
5 changed files with 173 additions and 34 deletions

View file

@ -540,14 +540,14 @@ const Class PromiseReactionRecord::class_ = {
static void
AddPromiseFlags(PromiseObject& promise, int32_t flag)
{
int32_t flags = promise.getFixedSlot(PromiseSlot_Flags).toInt32();
int32_t flags = promise.flags();
promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag));
}
static bool
PromiseHasAnyFlag(PromiseObject& promise, int32_t flag)
{
return promise.getFixedSlot(PromiseSlot_Flags).toInt32() & flag;
return promise.flags() & flag;
}
static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp);
@ -922,7 +922,7 @@ ResolvePromise(JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueO
// instead of getting the right list of reactions, we determine the
// resolution type to retrieve the right information from the
// reaction records.
RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
RootedValue reactionsVal(cx, promise->reactions());
// Steps 3-5.
// The same slot is used for the reactions list and the result, so setting
@ -930,7 +930,7 @@ ResolvePromise(JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueO
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason);
// Step 6.
int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
int32_t flags = promise->flags();
flags |= PROMISE_FLAG_RESOLVED;
if (state == JS::PromiseState::Fulfilled)
flags |= PROMISE_FLAG_FULFILLED;
@ -994,8 +994,7 @@ CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx)
if (!promise)
return nullptr;
AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION |
PROMISE_FLAG_DEFAULT_REJECT_FUNCTION);
AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS);
return promise;
}
@ -1643,9 +1642,9 @@ static MOZ_MUST_USE bool
AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
Handle<PromiseReactionRecord*> reaction);
static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleValue promise,
HandleObject blockedPromise,
HandleValue onFulfilled, HandleValue onRejected);
static MOZ_MUST_USE bool
BlockOnPromise(JSContext* cx, HandleValue promise, HandleObject blockedPromise,
HandleValue onFulfilled, HandleValue onRejected, bool onFulfilledReturnsUndefined);
static JSFunction*
GetResolveFunctionFromReject(JSFunction* reject)
@ -2210,14 +2209,12 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res
if (promise->state() != JS::PromiseState::Pending)
return true;
if (mode == ResolveMode) {
if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION))
return true;
return ResolvePromiseInternal(cx, promise, result);
}
if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION))
if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS))
return true;
if (mode == ResolveMode)
return ResolvePromiseInternal(cx, promise, result);
return RejectMaybeWrappedPromise(cx, promiseObj, result);
}
@ -2364,7 +2361,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
// Step q.
RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, true))
return false;
// Step r.
@ -2479,7 +2476,7 @@ PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObjec
RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
RootedValue rejectFunVal(cx, ObjectValue(*rejectFunc));
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal, true))
return false;
index++;
@ -2785,6 +2782,11 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
MOZ_ASSERT(C->isConstructor());
RootedValue CVal(cx, ObjectValue(*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);
RootedValue nextValue(cx);
RootedValue resolveFunVal(cx, ObjectValue(*resolve));
RootedValue rejectFunVal(cx, ObjectValue(*reject));
@ -2821,8 +2823,11 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
return false;
// Step i.
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal,
isDefaultResolveFn))
{
return false;
}
}
MOZ_ASSERT_UNREACHABLE("Shouldn't reach the end of PerformPromiseRace");
@ -3826,7 +3831,7 @@ PerformPromiseThenWithReaction(JSContext* cx, Handle<PromiseObject*> promise,
Handle<PromiseReactionRecord*> reaction)
{
JS::PromiseState state = promise->state();
int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
int32_t flags = promise->flags();
if (state == JS::PromiseState::Pending) {
// Steps 5,6 (reordered).
// Instead of creating separate reaction records for fulfillment and
@ -3842,7 +3847,7 @@ PerformPromiseThenWithReaction(JSContext* cx, Handle<PromiseObject*> promise,
MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled, state == JS::PromiseState::Rejected);
// Step 8.a. / 9.b.
RootedValue valueOrReason(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
RootedValue valueOrReason(cx, promise->valueOrReason());
// We might be operating on a promise from another compartment. In
// that case, we need to wrap the result/reason value before using it.
@ -3876,7 +3881,7 @@ PerformPromiseThenWithReaction(JSContext* cx, Handle<PromiseObject*> promise,
*/
static MOZ_MUST_USE bool
BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromise_,
HandleValue onFulfilled, HandleValue onRejected)
HandleValue onFulfilled, HandleValue onRejected, bool onFulfilledReturnsUndefined)
{
RootedObject promiseObj(cx, ToObject(cx, promiseVal));
if (!promiseObj)
@ -3898,7 +3903,7 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis
if (!C)
return false;
RootedObject resultPromise(cx, blockedPromise_);
RootedObject resultPromise(cx);
RootedObject resolveFun(cx);
RootedObject rejectFun(cx);
@ -3906,7 +3911,21 @@ BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromis
// rejected promises list.
bool addToDependent = true;
if (C == PromiseCtor && resultPromise->is<PromiseObject>()) {
// Skip the creation of a built-in Promise object if:
// 1. `C` is the built-in Promise constructor.
// 2. The `onFulfilled` handler doesn't return an object, which
// ensures no side-effects take place in ResolvePromiseInternal.
// 3. The blocked promise is a built-in Promise object.
// 4. The blocked promise doesn't use the default resolving functions,
// which in turn means RunResolutionFunction when called from
// PromiseRectionJob won't try to resolve the promise.
if (C == PromiseCtor &&
onFulfilledReturnsUndefined &&
blockedPromise_->is<PromiseObject>() &&
!PromiseHasAnyFlag(blockedPromise_->as<PromiseObject>(),
PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS))
{
resultPromise.set(blockedPromise_);
addToDependent = false;
} else {
// 25.4.5.3., step 4.
@ -3997,7 +4016,7 @@ AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
}
// 25.4.5.3.1 steps 7.a,b.
RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
RootedValue reactionsVal(cx, promise->reactions());
RootedNativeObject reactions(cx);
if (reactionsVal.isUndefined()) {
@ -4093,7 +4112,7 @@ PromiseObject::dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> v
if (state() != JS::PromiseState::Pending)
return true;
RootedValue reactionsVal(cx, getFixedSlot(PromiseSlot_ReactionsOrResult));
RootedValue reactionsVal(cx, reactions());
// If no reactions are pending, we don't have list and are done.
if (reactionsVal.isNullOrUndefined())
@ -4143,7 +4162,7 @@ PromiseObject::resolve(JSContext* cx, Handle<PromiseObject*> promise, HandleValu
if (promise->state() != JS::PromiseState::Pending)
return true;
if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION))
if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS))
return ResolvePromiseInternal(cx, promise, resolutionValue);
RootedObject resolveFun(cx, GetResolveFunctionFromPromise(promise));
@ -4172,7 +4191,7 @@ PromiseObject::reject(JSContext* cx, Handle<PromiseObject*> promise, HandleValue
if (promise->state() != JS::PromiseState::Pending)
return true;
if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION))
if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS))
return RejectMaybeWrappedPromise(cx, promise, rejectionValue);
RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction));