[Promise.any] Add Promise.any

CL adopted from joshualitt@: https://chromium-review.googlesource.com/c/v8/v8/+/2002932

Link to explainer is here: https://github.com/tc39/proposal-promise-any

Co-authored-by: Joshua Litt <joshualitt@chromium.org>

Bug: v8:9808
Change-Id: I6872020e857d4b131d5663f95fd58e6271ccb067
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2124834
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Sathya Gunasekaran  <gsathya@chromium.org>
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67502}
This commit is contained in:
Marja Hölttä 2020-04-30 15:09:58 +02:00 committed by Commit Bot
parent fd2548f332
commit 6f994a0bdf
26 changed files with 760 additions and 14 deletions

View File

@ -1049,6 +1049,7 @@ torque_files = [
"src/builtins/promise-abstract-operations.tq",
"src/builtins/promise-all.tq",
"src/builtins/promise-all-element-closure.tq",
"src/builtins/promise-any.tq",
"src/builtins/promise-constructor.tq",
"src/builtins/promise-finally.tq",
"src/builtins/promise-misc.tq",

View File

@ -255,6 +255,7 @@ constexpr 'CodeStubAssembler::ExtractFixedArrayFlag' {
const kBigIntMaxLength: constexpr intptr generates 'BigInt::kMaxLength';
extern enum MessageTemplate {
kAllPromisesRejected,
kInvalidArrayBufferLength,
kInvalidArrayLength,
kInvalidIndex,
@ -290,7 +291,7 @@ extern enum MessageTemplate {
kPromiseNonCallable,
kNotAPromise,
kResolverNotAFunction,
kTooManyElementsInPromiseAll,
kTooManyElementsInPromiseCombinator,
kToRadixFormatRange,
kCalledOnNonObject,
kRegExpGlobalInvokedOnNonGlobal,

View File

@ -1157,6 +1157,7 @@ namespace internal {
V(AsyncGeneratorAwaitCaught) \
V(AsyncGeneratorAwaitUncaught) \
V(PromiseAll) \
V(PromiseAny) \
V(PromiseConstructor) \
V(PromiseConstructorLazyDeoptContinuation) \
V(PromiseFulfillReactionJob) \

View File

@ -40,6 +40,18 @@ class PromiseBuiltins {
kPromiseAllResolveElementLength
};
enum PromiseAnyRejectElementContextSlots {
// Remaining elements count
kPromiseAnyRejectElementRemainingSlot = Context::MIN_CONTEXT_SLOTS,
// Promise capability from Promise.any
kPromiseAnyRejectElementCapabilitySlot,
// errors array from Promise.any
kPromiseAnyRejectElementErrorsArraySlot,
kPromiseAnyRejectElementLength
};
enum FunctionContextSlot {
kCapabilitySlot = Context::MIN_CONTEXT_SLOTS,

View File

@ -25,6 +25,10 @@ namespace growable_fixed_array {
this.array = this.ResizeFixedArray(this.capacity);
}
}
macro ToFixedArray(): FixedArray {
return this.ResizeFixedArray(this.length);
}
macro ToJSArray(implicit context: Context)(): JSArray {
const nativeContext: NativeContext = LoadNativeContext(context);
const map: Map =

View File

@ -187,7 +187,8 @@ namespace promise {
// separate context, but it doesn't seem likely that we need this,
// and it's unclear how the rest of the system deals with 2**21 live
// Promises anyway.
ThrowRangeError(MessageTemplate::kTooManyElementsInPromiseAll);
ThrowRangeError(
MessageTemplate::kTooManyElementsInPromiseCombinator, 'all');
}
// Set remainingElementsCount.[[Value]] to

370
src/builtins/promise-any.tq Normal file
View File

@ -0,0 +1,370 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include 'src/builtins/builtins-promise-gen.h'
namespace promise {
extern enum PromiseAnyRejectElementContextSlots extends int31
constexpr 'PromiseBuiltins::PromiseAnyRejectElementContextSlots' {
kPromiseAnyRejectElementRemainingSlot,
kPromiseAnyRejectElementCapabilitySlot,
kPromiseAnyRejectElementErrorsArraySlot,
kPromiseAnyRejectElementLength
}
extern operator '[]=' macro StoreContextElement(
Context, constexpr PromiseAnyRejectElementContextSlots, Object): void;
extern operator '[]' macro LoadContextElement(
Context, constexpr PromiseAnyRejectElementContextSlots): Object;
// Creates the context used by all Promise.any reject element closures,
// together with the errors array. Since all closures for a single Promise.any
// call use the same context, we need to store the indices for the individual
// closures somewhere else (we put them into the identity hash field of the
// closures), and we also need to have a separate marker for when the closure
// was called already (we slap the native context onto the closure in that
// case to mark it's done). See Promise.all which uses the same approach.
transitioning macro CreatePromiseAnyRejectElementContext(implicit context:
Context)(
capability: PromiseCapability, nativeContext: NativeContext): Context {
const rejectContext = AllocateSyntheticFunctionContext(
nativeContext,
PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength);
rejectContext[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementRemainingSlot] = SmiConstant(1);
rejectContext[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementCapabilitySlot] = capability;
// Will be set later.
rejectContext[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementErrorsArraySlot] = Undefined;
return rejectContext;
}
macro CreatePromiseAnyRejectElementFunction(implicit context: Context)(
rejectElementContext: Context, index: Smi,
nativeContext: NativeContext): JSFunction {
assert(index > 0);
assert(index < kPropertyArrayHashFieldMax);
const map = UnsafeCast<Map>(
nativeContext
[NativeContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX]);
const rejectInfo = UnsafeCast<SharedFunctionInfo>(
nativeContext
[NativeContextSlot::PROMISE_ANY_REJECT_ELEMENT_SHARED_FUN]);
const reject = AllocateFunctionWithMapAndContext(
map, rejectInfo, rejectElementContext);
assert(kPropertyArrayNoHashSentinel == 0);
reject.properties_or_hash = index;
return reject;
}
// https://tc39.es/proposal-promise-any/#sec-promise.any-reject-element-functions
transitioning javascript builtin
PromiseAnyRejectElementClosure(
js-implicit context: Context, receiver: JSAny,
target: JSFunction)(value: JSAny): JSAny {
// 1. Let F be the active function object.
// 2. Let alreadyCalled be F.[[AlreadyCalled]].
// 3. If alreadyCalled.[[Value]] is true, return undefined.
// We use the function's context as the marker to remember whether this
// reject element closure was already called. It points to the reject
// element context (which is a FunctionContext) until it was called the
// first time, in which case we make it point to the native context here
// to mark this reject element closure as done.
if (IsNativeContext(context)) deferred {
return Undefined;
}
assert(
context.length ==
PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength);
// 4. Set alreadyCalled.[[Value]] to true.
const nativeContext = LoadNativeContext(context);
target.context = nativeContext;
// 5. Let index be F.[[Index]].
assert(kPropertyArrayNoHashSentinel == 0);
const identityHash =
LoadJSReceiverIdentityHash(target) otherwise unreachable;
assert(identityHash > 0);
const index = identityHash - 1;
// 6. Let errors be F.[[Errors]].
const errorsArray = UnsafeCast<FixedArray>(
context[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementErrorsArraySlot]);
// 7. Let promiseCapability be F.[[Capability]].
// 8. Let remainingElementsCount be F.[[RemainingElements]].
let remainingElementsCount =
UnsafeCast<Smi>(context[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementRemainingSlot]);
// 9. Set errors[index] to x.
errorsArray.objects[index] = value;
// 10. Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] - 1.
remainingElementsCount = remainingElementsCount - 1;
context[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementRemainingSlot] = remainingElementsCount;
// 11. If remainingElementsCount.[[Value]] is 0, then
if (remainingElementsCount == 0) {
// a. Let error be a newly created AggregateError object.
// b. Set error.[[AggregateErrors]] to errors.
const error = ConstructAggregateError(errorsArray);
// c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »).
const capability = UnsafeCast<PromiseCapability>(
context[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementCapabilitySlot]);
Call(context, UnsafeCast<Callable>(capability.reject), Undefined, error);
}
// 12. Return undefined.
return Undefined;
}
transitioning macro PerformPromiseAny(implicit context: Context)(
iteratorRecord: iterator::IteratorRecord, constructor: Constructor,
resultCapability: PromiseCapability): JSAny labels
Reject(Object) {
// 1. Assert: ! IsConstructor(constructor) is true.
// 2. Assert: resultCapability is a PromiseCapability Record.
const nativeContext = LoadNativeContext(context);
// 3. Let errors be a new empty List.
let growableErrorsArray = growable_fixed_array::NewGrowableFixedArray();
// 4. Let remainingElementsCount be a new Record { [[Value]]: 1 }.
const rejectElementContext =
CreatePromiseAnyRejectElementContext(resultCapability, nativeContext);
// 5. Let index be 0.
// (We subtract 1 in the PromiseAnyRejectElementClosure).
let index: Smi = 1;
try {
// We can skip the "resolve" lookup on {constructor} if it's the
// Promise constructor and the Promise.resolve protector is intact,
// as that guards the lookup path for the "resolve" property on the
// Promise constructor.
let promiseResolveFunction: JSAny = Undefined;
if (!IsPromiseResolveLookupChainIntact(nativeContext, constructor))
deferred {
// 6. Let promiseResolve be ? Get(constructor, `"resolve"`).
const promiseResolve = GetProperty(constructor, kResolveString);
// 7. If IsCallable(promiseResolve) is false, throw a
// TypeError exception.
promiseResolveFunction = Cast<Callable>(promiseResolve)
otherwise ThrowTypeError(
MessageTemplate::kCalledNonCallable, 'resolve');
}
const fastIteratorResultMap = UnsafeCast<Map>(
nativeContext[NativeContextSlot::ITERATOR_RESULT_MAP_INDEX]);
// 8. Repeat,
while (true) {
let nextValue: JSAny;
try {
// a. Let next be IteratorStep(iteratorRecord).
// b. If next is an abrupt completion, set
// iteratorRecord.[[Done]] to true.
// c. ReturnIfAbrupt(next).
// d. if next is false, then [continues below in "Done"]
const next: JSReceiver = iterator::IteratorStep(
iteratorRecord, fastIteratorResultMap) otherwise goto Done;
// e. Let nextValue be IteratorValue(next).
// f. If nextValue is an abrupt completion, set
// iteratorRecord.[[Done]] to true.
// g. ReturnIfAbrupt(nextValue).
nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
} catch (e) {
goto Reject(e);
}
// We store the indices as identity hash on the reject element
// closures. Thus, we need this limit.
if (index == kPropertyArrayHashFieldMax) {
// If there are too many elements (currently more than
// 2**21-1), raise a RangeError here (which is caught later and
// turned into a rejection of the resulting promise). We could
// gracefully handle this case as well and support more than
// this number of elements by going to a separate function and
// pass the larger indices via a separate context, but it
// doesn't seem likely that we need this, and it's unclear how
// the rest of the system deals with 2**21 live Promises
// anyway.
ThrowRangeError(
MessageTemplate::kTooManyElementsInPromiseCombinator, 'any');
}
// h. Append undefined to errors.
growableErrorsArray.Push(Undefined);
let nextPromise: JSAny;
// i. Let nextPromise be ? Call(constructor, promiseResolve,
// «nextValue »).
nextPromise =
CallResolve(constructor, promiseResolveFunction, nextValue);
// j. Let steps be the algorithm steps defined in Promise.any
// Reject Element Functions.
// k. Let rejectElement be ! CreateBuiltinFunction(steps, «
// [[AlreadyCalled]], [[Index]],
// [[Errors]], [[Capability]], [[RemainingElements]] »).
// l. Set rejectElement.[[AlreadyCalled]] to a new Record {
// [[Value]]: false }.
// m. Set rejectElement.[[Index]] to index.
// n. Set rejectElement.[[Errors]] to errors.
// o. Set rejectElement.[[Capability]] to resultCapability.
// p. Set rejectElement.[[RemainingElements]] to
// remainingElementsCount.
const rejectElement = CreatePromiseAnyRejectElementFunction(
rejectElementContext, index, nativeContext);
// q. Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] + 1.
const remainingElementsCount = UnsafeCast<Smi>(
rejectElementContext[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementRemainingSlot]);
rejectElementContext[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementRemainingSlot] =
remainingElementsCount + 1;
// r. Perform ? Invoke(nextPromise, "then", «
// resultCapability.[[Resolve]], rejectElement »).
let thenResult: JSAny;
const then = GetProperty(nextPromise, kThenString);
thenResult = Call(
context, then, nextPromise,
UnsafeCast<JSAny>(resultCapability.resolve), rejectElement);
// s. Increase index by 1.
index += 1;
// For catch prediction, mark that rejections here are
// semantically handled by the combined Promise.
if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred {
SetPropertyStrict(
context, thenResult, kPromiseHandledBySymbol,
resultCapability.promise);
SetPropertyStrict(
context, rejectElement, kPromiseForwardingHandlerSymbol, True);
}
}
}
label Done {}
catch (e) deferred {
iterator::IteratorCloseOnException(iteratorRecord, e)
otherwise Reject;
}
// (8.d)
// i. Set iteratorRecord.[[Done]] to true.
// ii. Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] - 1.
let remainingElementsCount = UnsafeCast<Smi>(
rejectElementContext[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementRemainingSlot]);
remainingElementsCount -= 1;
rejectElementContext[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementRemainingSlot] =
remainingElementsCount;
const errorsArray = growableErrorsArray.ToFixedArray();
rejectElementContext[PromiseAnyRejectElementContextSlots::
kPromiseAnyRejectElementErrorsArraySlot] =
errorsArray;
// iii. If remainingElementsCount.[[Value]] is 0, then
if (remainingElementsCount == 0) deferred {
// 1. Let error be a newly created AggregateError object.
// 2. Set error.[[AggregateErrors]] to errors.
const error = ConstructAggregateError(errorsArray);
// 3. Return ThrowCompletion(error).
goto Reject(error);
}
// iv. Return resultCapability.[[Promise]].
return resultCapability.promise;
}
// https://tc39.es/proposal-promise-any/#sec-promise.any
transitioning javascript builtin
PromiseAny(js-implicit context: Context, receiver: JSAny)(iterable: JSAny):
JSAny {
// 1. Let C be the this value.
const receiver = Cast<JSReceiver>(receiver)
otherwise ThrowTypeError(
MessageTemplate::kCalledOnNonObject, 'Promise.any');
// 2. Let promiseCapability be ? NewPromiseCapability(C).
const capability = NewPromiseCapability(receiver, False);
// NewPromiseCapability guarantees that receiver is Constructor
assert(Is<Constructor>(receiver));
const constructor = UnsafeCast<Constructor>(receiver);
try {
let iteratorRecord: iterator::IteratorRecord;
try {
// 3. Let iteratorRecord be GetIterator(iterable).
// 4. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
// (catch below)
iteratorRecord = iterator::GetIterator(iterable);
// 5. Let result be PerformPromiseAny(iteratorRecord, C,
// promiseCapability).
// 6. If result is an abrupt completion, then
// a. If iteratorRecord.[[Done]] is false, set result to
// IteratorClose(iteratorRecord, result).
// b. IfAbruptRejectPromise(result, promiseCapability).
// [Iterator closing handled by PerformPromiseAny]
// 7. Return Completion(result).
return PerformPromiseAny(iteratorRecord, constructor, capability)
otherwise Reject;
} catch (e) deferred {
goto Reject(e);
}
}
label Reject(e: Object) deferred {
// Exception must be bound to a JS value.
assert(e != TheHole);
Call(
context, UnsafeCast<Callable>(capability.reject), Undefined,
UnsafeCast<JSAny>(e));
return capability.promise;
}
}
transitioning macro ConstructAggregateError(implicit context: Context)(
errorsArray: FixedArray): JSObject {
const obj: JSAggregateError = error::ConstructInternalAggregateErrorHelper(
context, SmiConstant(MessageTemplate::kAllPromisesRejected));
obj.errors = errorsArray;
return obj;
}
}

View File

@ -498,7 +498,8 @@ namespace internal {
T(TooManySpreads, \
"Literal containing too many nested spreads (up to 65534 allowed)") \
T(TooManyVariables, "Too many variables declared (only 4194303 allowed)") \
T(TooManyElementsInPromiseAll, "Too many elements passed to Promise.all") \
T(TooManyElementsInPromiseCombinator, \
"Too many elements passed to Promise.%") \
T(TypedArrayTooShort, \
"Derived TypedArray constructor created an array which was too small") \
T(UnexpectedEOS, "Unexpected end of input") \
@ -591,7 +592,9 @@ namespace internal {
"WeakRef: target must be an object") \
T(OptionalChainingNoNew, "Invalid optional chain from new expression") \
T(OptionalChainingNoSuper, "Invalid optional chain from super property") \
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain")
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain") \
/* AggregateError */ \
T(AllPromisesRejected, "All promises were rejected")
enum class MessageTemplate {
#define TEMPLATE(NAME, STRING) k##NAME,

View File

@ -925,12 +925,25 @@ MaybeHandle<Object> ErrorUtils::FormatStackTrace(Isolate* isolate,
}
Handle<String> MessageFormatter::Format(Isolate* isolate, MessageTemplate index,
Handle<Object> arg) {
Handle<Object> arg0,
Handle<Object> arg1,
Handle<Object> arg2) {
Factory* factory = isolate->factory();
Handle<String> result_string = Object::NoSideEffectsToString(isolate, arg);
Handle<String> arg0_string = factory->empty_string();
if (!arg0.is_null()) {
arg0_string = Object::NoSideEffectsToString(isolate, arg0);
}
Handle<String> arg1_string = factory->empty_string();
if (!arg1.is_null()) {
arg1_string = Object::NoSideEffectsToString(isolate, arg1);
}
Handle<String> arg2_string = factory->empty_string();
if (!arg2.is_null()) {
arg2_string = Object::NoSideEffectsToString(isolate, arg2);
}
MaybeHandle<String> maybe_result_string = MessageFormatter::Format(
isolate, index, result_string, factory->empty_string(),
factory->empty_string());
isolate, index, arg0_string, arg1_string, arg2_string);
Handle<String> result_string;
if (!maybe_result_string.ToHandle(&result_string)) {
DCHECK(isolate->has_pending_exception());
isolate->clear_pending_exception();

View File

@ -317,7 +317,9 @@ class MessageFormatter {
Handle<String> arg2);
static Handle<String> Format(Isolate* isolate, MessageTemplate index,
Handle<Object> arg);
Handle<Object> arg0,
Handle<Object> arg1 = Handle<Object>(),
Handle<Object> arg2 = Handle<Object>());
};
// A message handler is a convenience interface for accessing the list

View File

@ -4414,6 +4414,22 @@ void Genesis::InitializeGlobal_harmony_promise_any() {
JSObject::DefineAccessor(prototype, factory->errors_string(), getter,
factory->undefined_value(), DONT_ENUM);
{
Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo(
isolate_, Builtins::kPromiseAnyRejectElementClosure,
factory->empty_string(), 1);
native_context()->set_promise_any_reject_element_shared_fun(*info);
}
Handle<JSFunction> promise_fun(
JSFunction::cast(
isolate()->native_context()->get(Context::PROMISE_FUNCTION_INDEX)),
isolate());
InstallFunctionWithBuiltinId(isolate_, promise_fun, "any",
Builtins::kPromiseAny, 1, true);
DCHECK(promise_fun->HasFastProperties());
}
void Genesis::InitializeGlobal_harmony_promise_all_settled() {

View File

@ -239,6 +239,8 @@ enum ContextLookupFlags {
promise_all_settled_resolve_element_shared_fun) \
V(PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
promise_all_settled_reject_element_shared_fun) \
V(PROMISE_ANY_REJECT_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
promise_any_reject_element_shared_fun) \
V(PROMISE_PROTOTYPE_INDEX, JSObject, promise_prototype) \
V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \

View File

@ -24,6 +24,7 @@ const MIN_CONTEXT_SLOTS: constexpr int31
generates 'Context::MIN_CONTEXT_SLOTS';
extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
AGGREGATE_ERROR_FUNCTION_INDEX,
ARRAY_BUFFER_FUN_INDEX,
ARRAY_BUFFER_NOINIT_FUN_INDEX,
ARRAY_BUFFER_MAP_INDEX,
@ -50,6 +51,7 @@ extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN,
PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN,
PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN,
PROMISE_ANY_REJECT_ELEMENT_SHARED_FUN,
PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX,
PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX,
PROMISE_CATCH_FINALLY_SHARED_FUN,

View File

@ -38,6 +38,8 @@ namespace error {
const errors: JSAny = arguments[0];
const errorsArray =
iterator::IterableToFixedArrayWithSymbolLookupSlow(errors);
// errorsArray must be marked copy-on-write, since the "errors" getter
// creates a thin JSArray wrapper around it.
MakeFixedArrayCOW(errorsArray);
// 5. Set O.[[AggregateErrors]] to errorsList.
@ -70,7 +72,10 @@ namespace error {
}
extern runtime ConstructAggregateErrorHelper(
Context, JSFunction, JSAny, JSAny): JSAggregateError;
Context, JSFunction, JSAny, Object): JSAggregateError;
extern runtime ConstructInternalAggregateErrorHelper(Context, Object):
JSAggregateError;
extern macro MakeFixedArrayCOW(FixedArray);

View File

@ -279,5 +279,43 @@ RUNTIME_FUNCTION(Runtime_ConstructAggregateErrorHelper) {
return *result;
}
// A helper function to be called when constructing AggregateError objects. This
// takes care of the Error-related construction, e.g., stack traces.
RUNTIME_FUNCTION(Runtime_ConstructInternalAggregateErrorHelper) {
DCHECK(FLAG_harmony_promise_any);
HandleScope scope(isolate);
DCHECK_GE(args.length(), 1);
CONVERT_ARG_HANDLE_CHECKED(Smi, message, 0);
Handle<Object> arg0;
if (args.length() >= 2) {
DCHECK(args[1].IsObject());
arg0 = args.at<Object>(1);
}
Handle<Object> arg1;
if (args.length() >= 3) {
DCHECK(args[2].IsObject());
arg1 = args.at<Object>(2);
}
Handle<Object> arg2;
if (args.length() >= 4) {
CHECK(args[3].IsObject());
arg2 = args.at<Object>(3);
}
Handle<Object> message_string = MessageFormatter::Format(
isolate, MessageTemplate(message->value()), arg0, arg1, arg2);
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
ErrorUtils::Construct(isolate, isolate->aggregate_error_function(),
isolate->aggregate_error_function(),
message_string));
return *result;
}
} // namespace internal
} // namespace v8

View File

@ -367,7 +367,8 @@ namespace internal {
F(ResolvePromise, 2, 1) \
F(PromiseRejectAfterResolved, 2, 1) \
F(PromiseResolveAfterResolved, 2, 1) \
F(ConstructAggregateErrorHelper, 3, 1)
F(ConstructAggregateErrorHelper, 3, 1) \
F(ConstructInternalAggregateErrorHelper, -1 /* <= 4*/, 1)
#define FOR_EACH_INTRINSIC_PROXY(F, I) \
F(CheckProxyGetSetTrapResult, 2, 1) \

View File

@ -0,0 +1,39 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Flags: --harmony-promise-any
// Test debug events when we only listen to uncaught exceptions and a
// Promise p3 created by Promise.any has a catch handler, and is rejected
// because the Promise p2 passed to Promise.any is rejected. We
// expect no Exception debug event to be triggered, since p3 and by
// extension p2 have a catch handler.
let Debug = debug.Debug;
let expected_events = 2;
let p1 = Promise.resolve();
p1.name = "p1";
let p2 = p1.then(function() {
throw new Error("caught");
});
p2.name = "p2";
let p3 = Promise.any([p2]);
p3.name = "p3";
p3.catch(function(e) {});
function listener(event, exec_state, event_data, data) {
try {
assertTrue(event != Debug.DebugEvent.Exception)
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
Debug.setBreakOnUncaughtException();
Debug.setListener(listener);

View File

@ -0,0 +1,67 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Flags: --harmony-promise-any
// Test debug events when we only listen to uncaught exceptions and a
// Promise p3 created by Promise.any has no catch handler, and is rejected
// because the Promise p2 passed to Promise.any is rejected.
// We expect one event for p2; the system recognizes the rejection of p3
// to be redundant and based on the rejection of p2 and does not trigger
// an additional rejection.
let Debug = debug.Debug;
let expected_events = 1;
let log = [];
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Exception) return;
try {
expected_events--;
assertTrue(expected_events >= 0);
assertEquals("uncaught", event_data.exception().message);
// Assert that the debug event is triggered at the throw site.
assertTrue(exec_state.frame(0).sourceLineText().indexOf("// event") > 0);
assertTrue(event_data.uncaught());
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
Debug.setBreakOnUncaughtException();
Debug.setListener(listener);
let p1 = Promise.resolve();
p1.name = "p1";
let p2 = p1.then(function() {
log.push("throw");
throw new Error("uncaught"); // event
});
p2.name = "p2";
let p3 = Promise.any([p2]);
p3.name = "p3";
log.push("end main");
function testDone(iteration) {
function checkResult() {
try {
assertTrue(iteration < 10);
if (expected_events === 0) {
assertEquals(["end main", "throw"], log);
} else {
testDone(iteration + 1);
}
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
%EnqueueMicrotask(checkResult);
}
testDone(0);

View File

@ -69,6 +69,11 @@
assertEquals('hello', error.message);
})();
(function TestTwoParametersMessageIsSMI() {
let error = new AggregateError([], 44);
assertEquals('44', error.message);
})();
(function TestTwoParametersMessageUndefined() {
let error = new AggregateError([], undefined);
assertFalse(Object.prototype.hasOwnProperty.call(error, 'message'));

View File

@ -0,0 +1,21 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --harmony-promise-any
load('test/mjsunit/test-async.js');
// Make sure we properly throw a RangeError when overflowing the maximum
// number of elements for Promise.any, which is capped at 2^21 bits right
// now, since we store the indices as identity hash on the resolve element
// closures.
const a = new Array(2 ** 21 - 1);
const p = Promise.resolve(1);
for (let i = 0; i < a.length; ++i) a[i] = p;
testAsync(assert => {
assert.plan(1);
Promise.any(a).then(assert.unreachable, reason => {
assert.equals(true, reason instanceof RangeError);
});
});

View File

@ -0,0 +1,18 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --harmony-promise-any
load('test/mjsunit/test-async.js');
// Test that pre-allocation of the errors array works even if it needs to be
// allocated in large object space.
const a = new Array(64 * 1024);
a.fill(Promise.reject(1));
testAsync(assert => {
assert.plan(1);
Promise.any(a).then(assert.unreachable, b => {
assert.equals(a.length, b.errors.length);
});
});

View File

@ -0,0 +1,30 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --harmony-promise-any
load('test/mjsunit/test-async.js');
// Promise.any should call IteratorClose if Promise.resolve is not callable.
let returnCount = 0;
let iter = {
[Symbol.iterator]() {
return {
return() {
returnCount++;
}
};
}
};
Promise.resolve = "certainly not callable";
testAsync(assert => {
assert.plan(2);
Promise.any(iter).then(assert.unreachable, reason => {
assert.equals(true, reason instanceof TypeError);
assert.equals(1, returnCount);
});
});

View File

@ -0,0 +1,94 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Flags: --allow-natives-syntax --harmony-promise-any
load('test/mjsunit/test-async.js');
(function() {
testAsync(assert => {
assert.plan(1);
Promise.any([]).then(
assert.unreachable,
(x) => { assert.equals(0, x.errors.length); }
);
});
})();
(function() {
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
testAsync(assert => {
assert.plan(1);
Promise.any([p1, p2, p3]).then(
(x) => { assert.equals(1, x); },
assert.unreachable);
});
})();
(function() {
let outsideResolve;
let outsideReject;
let p1 = new Promise(() => {});
let p2 = new Promise(function(resolve, reject) {
outsideResolve = resolve;
outsideReject = reject;
});
let p3 = new Promise(() => {});
testAsync(assert => {
assert.plan(1);
Promise.any([p1, p2, p3]).then(
(x) => { assert.equals(2, x); },
assert.unreachable
);
outsideResolve(2);
});
})();
(function() {
const p1 = Promise.reject(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
testAsync(assert => {
assert.plan(1);
Promise.any([p1, p2, p3]).then(
(x) => { assert.equals(2, x); },
assert.unreachable);
});
})();
(function() {
const p1 = Promise.reject(1);
const p2 = Promise.reject(2);
const p3 = Promise.reject(3);
testAsync(assert => {
assert.plan(4);
Promise.any([p1, p2, p3]).then(
assert.unreachable,
(x) => {
assert.equals(3, x.errors.length);
assert.equals(1, x.errors[0]);
assert.equals(2, x.errors[1]);
assert.equals(3, x.errors[2]);
}
);
});
})();
(function() {
testAsync(assert => {
assert.plan(1);
(async function() {
const p1 = Promise.reject(1);
const p2 = Promise.reject(2);
const p3 = Promise.reject(3);
try {
await Promise.any([p1, p2, p3]);
} catch (error) {
assert.equals(1, 1);
}
})();
});
})();

View File

@ -90,6 +90,7 @@
# Long-running tests.
# We really should find better solutions for these.
'es6/promise-all-overflow-1': [SKIP],
'harmony/promise-any-overflow-1': [SKIP],
'migrations': [SKIP],
'regress/regress-2073': [SKIP],
@ -117,6 +118,7 @@
'asm/sqlite3/*': [PASS, SLOW, NO_VARIANTS],
'compiler/regress-9017': [PASS, SLOW],
'es6/promise-all-overflow-2': [PASS, SLOW, ['arch != x64', SKIP]],
'harmony/promise-any-overflow-2': [PASS, SLOW, ['arch != x64', SKIP]],
'copy-on-write-assert': [PASS, SLOW],
'es6/typedarray-construct-offset-not-smi': [PASS, SLOW],
'harmony/futex': [PASS, SLOW],

View File

@ -536,9 +536,6 @@
# https://bugs.chromium.org/p/v8/issues/detail?id=9818
'built-ins/AsyncFunction/proto-from-ctor-realm': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=9808
'built-ins/Promise/any/*': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=10111
# super() should evaluate arguments before checking IsConstructable
'language/expressions/super/call-proto-not-ctor': [FAIL],

View File

@ -63,6 +63,7 @@ FEATURE_FLAGS = {
'class-static-methods-private': '--harmony-private-methods',
'AggregateError': '--harmony-promise-any',
'logical-assignment-operators': '--harmony-logical-assignment',
'Promise.any': '--harmony-promise-any',
}
SKIPPED_FEATURES = set([])