[Promise.allSettled] Fix [[AlreadyCalled]] checking in element closures

Bug: chromium:1105318
Change-Id: I7b1c57b7ff7beaaa53c19a270d5a8c36b11baf17
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2301082
Reviewed-by: Sathya Gunasekaran  <gsathya@chromium.org>
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68903}
This commit is contained in:
Shu-yu Guo 2020-07-15 17:12:44 -07:00 committed by Commit Bot
parent 8b338b25ba
commit 26df3fdc25

View File

@ -82,7 +82,8 @@ const kPropertyArrayHashFieldMax: constexpr int31
transitioning macro PromiseAllResolveElementClosure<F: type>(
implicit context: Context)(
value: JSAny, function: JSFunction, wrapResultFunctor: F): JSAny {
value: JSAny, function: JSFunction, wrapResultFunctor: F,
hasResolveAndRejectClosures: constexpr bool): JSAny {
// We use the {function}s context as the marker to remember whether this
// resolve element closure was already called. It points to the resolve
// element context (which is a FunctionContext) until it was called the
@ -98,10 +99,6 @@ transitioning macro PromiseAllResolveElementClosure<F: type>(
const nativeContext = LoadNativeContext(context);
function.context = nativeContext;
// Update the value depending on whether Promise.all or
// Promise.allSettled is called.
const updatedValue = wrapResultFunctor.Call(nativeContext, value);
// Determine the index from the {function}.
assert(kPropertyArrayNoHashSentinel == 0);
const identityHash =
@ -123,6 +120,27 @@ transitioning macro PromiseAllResolveElementClosure<F: type>(
context.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesSlot] = values;
}
// Promise.allSettled, for each input element, has both a resolve and a reject
// closure that share an [[AlreadyCalled]] boolean. That is, the input element
// can only be settled once: after resolve is called, reject returns early,
// and vice versa. Using {function}'s context as the marker only tracks
// per-closure instead of per-element. When the second resolve/reject closure
// is called on the same index, values.object[index] will already exist and
// will not be the hole value. In that case, return early. Everything up to
// this point is not yet observable to user code. This is not a problem for
// Promise.all since Promise.all has a single resolve closure (no reject) per
// element.
if (hasResolveAndRejectClosures) {
if (values.objects[index] != TheHole) deferred {
return Undefined;
}
}
// Update the value depending on whether Promise.all or
// Promise.allSettled is called.
const updatedValue = wrapResultFunctor.Call(nativeContext, value);
values.objects[index] = updatedValue;
remainingElementsCount = remainingElementsCount - 1;
@ -148,7 +166,7 @@ PromiseAllResolveElementClosure(
js-implicit context: Context, receiver: JSAny,
target: JSFunction)(value: JSAny): JSAny {
return PromiseAllResolveElementClosure(
value, target, PromiseAllWrapResultAsFulfilledFunctor{});
value, target, PromiseAllWrapResultAsFulfilledFunctor{}, false);
}
transitioning javascript builtin
@ -156,7 +174,7 @@ PromiseAllSettledResolveElementClosure(
js-implicit context: Context, receiver: JSAny,
target: JSFunction)(value: JSAny): JSAny {
return PromiseAllResolveElementClosure(
value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{});
value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}, true);
}
transitioning javascript builtin
@ -164,6 +182,6 @@ PromiseAllSettledRejectElementClosure(
js-implicit context: Context, receiver: JSAny,
target: JSFunction)(value: JSAny): JSAny {
return PromiseAllResolveElementClosure(
value, target, PromiseAllSettledWrapResultAsRejectedFunctor{});
value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true);
}
}