From 1b60c232493fe0b696c3cdba0ae47bd3106bdd7b Mon Sep 17 00:00:00 2001 From: Benedikt Meurer Date: Thu, 5 Jul 2018 09:42:52 +0200 Subject: [PATCH 1/3] src: prepare for V8 Swallowed Rejection Hook This is done in preparation for landing https://chromium-review.googlesource.com/c/v8/v8/+/1126099 on the V8 side, which extends the existing PromiseRejectEvent mechanism with new hooks for reject/resolve after a Promise was previously resolved already. Refs: https://github.com/nodejs/promises-debugging/issues/8 Design: https://goo.gl/2stLUY --- src/bootstrapper.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 8bcc0493f5be..5f40d62a82fd 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -69,7 +69,7 @@ void PromiseRejectCallback(PromiseRejectMessage message) { callback = env->promise_reject_handled_function(); value = Undefined(isolate); } else { - UNREACHABLE(); + return; } Local args[] = { promise, value }; From 68d10b6b924e2b5f75c21b92c893d6ceb023e11d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Mon, 16 Jul 2018 21:57:05 +0200 Subject: [PATCH 2/3] deps: cherry-pick 2075910 from upstream V8 Original commit message: [turbofan] Remove optimization of default Promise capability functions. The JSCallReducer could in theory inline the default resolve and reject functions passed to the executor in the Promise constructor. But that inlining is almost never triggered because we don't have SFI based feedback in the CallIC. Also the use of the Promise constructor is discouraged, so we shouldn't really need to squeeze the last bit of performance out of this even in the future. Getting rid of this optimization will make significantly easier to implement the Swallowed Rejection Hook, as there's less churn on the TurboFan side then. Bug: v8:7919 Change-Id: If0c54f1c6c7ce95686cd74232be6b8693ac688c9 Reviewed-on: https://chromium-review.googlesource.com/1125926 Commit-Queue: Benedikt Meurer Reviewed-by: Jaroslav Sevcik Cr-Commit-Position: refs/heads/master@{#54210} Refs: https://github.com/v8/v8/commit/2075910f3d070159bebd80e128dd09fdd87be56e --- common.gypi | 2 +- deps/v8/src/compiler/js-call-reducer.cc | 118 ------------------------ deps/v8/src/compiler/js-call-reducer.h | 2 - 3 files changed, 1 insertion(+), 121 deletions(-) diff --git a/common.gypi b/common.gypi index a782cfbecb85..17948b9da3b7 100644 --- a/common.gypi +++ b/common.gypi @@ -28,7 +28,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.15', + 'v8_embedder_string': '-node.16', # Enable disassembler for `--print-code` v8 options 'v8_enable_disassembler': 1, diff --git a/deps/v8/src/compiler/js-call-reducer.cc b/deps/v8/src/compiler/js-call-reducer.cc index 451ec80a8d4f..8e5034cde07d 100644 --- a/deps/v8/src/compiler/js-call-reducer.cc +++ b/deps/v8/src/compiler/js-call-reducer.cc @@ -3519,10 +3519,6 @@ Reduction JSCallReducer::ReduceJSCall(Node* node, return ReduceAsyncFunctionPromiseCreate(node); case Builtins::kAsyncFunctionPromiseRelease: return ReduceAsyncFunctionPromiseRelease(node); - case Builtins::kPromiseCapabilityDefaultReject: - return ReducePromiseCapabilityDefaultReject(node); - case Builtins::kPromiseCapabilityDefaultResolve: - return ReducePromiseCapabilityDefaultResolve(node); case Builtins::kPromiseInternalConstructor: return ReducePromiseInternalConstructor(node); case Builtins::kPromiseInternalReject: @@ -5252,120 +5248,6 @@ Reduction JSCallReducer::ReduceAsyncFunctionPromiseRelease(Node* node) { return Replace(value); } -// ES section #sec-promise-reject-functions -Reduction JSCallReducer::ReducePromiseCapabilityDefaultReject(Node* node) { - DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); - Node* target = NodeProperties::GetValueInput(node, 0); - Node* resolution = node->op()->ValueInputCount() > 2 - ? NodeProperties::GetValueInput(node, 2) - : jsgraph()->UndefinedConstant(); - Node* frame_state = NodeProperties::GetFrameStateInput(node); - Node* effect = NodeProperties::GetEffectInput(node); - Node* control = NodeProperties::GetControlInput(node); - - // We need to execute in the {target}s context. - Node* context = effect = graph()->NewNode( - simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target, - effect, control); - - // Grab the promise closed over by {target}. - Node* promise = effect = - graph()->NewNode(simplified()->LoadField(AccessBuilder::ForContextSlot( - PromiseBuiltinsAssembler::kPromiseSlot)), - context, effect, control); - - // Check if the {promise} is still pending or already settled. - Node* check = graph()->NewNode(simplified()->ReferenceEqual(), promise, - jsgraph()->UndefinedConstant()); - Node* branch = - graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); - - Node* if_true = graph()->NewNode(common()->IfTrue(), branch); - Node* etrue = effect; - - Node* if_false = graph()->NewNode(common()->IfFalse(), branch); - Node* efalse = effect; - { - // Mark the {promise} as settled. - efalse = graph()->NewNode( - simplified()->StoreField(AccessBuilder::ForContextSlot( - PromiseBuiltinsAssembler::kPromiseSlot)), - context, jsgraph()->UndefinedConstant(), efalse, if_false); - - // Check if we should emit a debug event. - Node* debug_event = efalse = - graph()->NewNode(simplified()->LoadField(AccessBuilder::ForContextSlot( - PromiseBuiltinsAssembler::kDebugEventSlot)), - context, efalse, if_false); - - // Actually reject the {promise}. - efalse = - graph()->NewNode(javascript()->RejectPromise(), promise, resolution, - debug_event, context, frame_state, efalse, if_false); - } - - control = graph()->NewNode(common()->Merge(2), if_true, if_false); - effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); - - Node* value = jsgraph()->UndefinedConstant(); - ReplaceWithValue(node, value, effect, control); - return Replace(value); -} - -// ES section #sec-promise-resolve-functions -Reduction JSCallReducer::ReducePromiseCapabilityDefaultResolve(Node* node) { - DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); - Node* target = NodeProperties::GetValueInput(node, 0); - Node* resolution = node->op()->ValueInputCount() > 2 - ? NodeProperties::GetValueInput(node, 2) - : jsgraph()->UndefinedConstant(); - Node* frame_state = NodeProperties::GetFrameStateInput(node); - Node* effect = NodeProperties::GetEffectInput(node); - Node* control = NodeProperties::GetControlInput(node); - - // We need to execute in the {target}s context. - Node* context = effect = graph()->NewNode( - simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target, - effect, control); - - // Grab the promise closed over by {target}. - Node* promise = effect = - graph()->NewNode(simplified()->LoadField(AccessBuilder::ForContextSlot( - PromiseBuiltinsAssembler::kPromiseSlot)), - context, effect, control); - - // Check if the {promise} is still pending or already settled. - Node* check = graph()->NewNode(simplified()->ReferenceEqual(), promise, - jsgraph()->UndefinedConstant()); - Node* branch = - graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); - - Node* if_true = graph()->NewNode(common()->IfTrue(), branch); - Node* etrue = effect; - - Node* if_false = graph()->NewNode(common()->IfFalse(), branch); - Node* efalse = effect; - { - // Mark the {promise} as settled. - efalse = graph()->NewNode( - simplified()->StoreField(AccessBuilder::ForContextSlot( - PromiseBuiltinsAssembler::kPromiseSlot)), - context, jsgraph()->UndefinedConstant(), efalse, if_false); - - // Actually resolve the {promise}. - efalse = - graph()->NewNode(javascript()->ResolvePromise(), promise, resolution, - context, frame_state, efalse, if_false); - } - - control = graph()->NewNode(common()->Merge(2), if_true, if_false); - effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); - - Node* value = jsgraph()->UndefinedConstant(); - ReplaceWithValue(node, value, effect, control); - return Replace(value); -} - Node* JSCallReducer::CreateArtificialFrameState( Node* node, Node* outer_frame_state, int parameter_count, BailoutId bailout_id, FrameStateType frame_state_type, diff --git a/deps/v8/src/compiler/js-call-reducer.h b/deps/v8/src/compiler/js-call-reducer.h index 36a5ac2e7a5d..e2810dc34c74 100644 --- a/deps/v8/src/compiler/js-call-reducer.h +++ b/deps/v8/src/compiler/js-call-reducer.h @@ -130,8 +130,6 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer { Reduction ReduceAsyncFunctionPromiseCreate(Node* node); Reduction ReduceAsyncFunctionPromiseRelease(Node* node); - Reduction ReducePromiseCapabilityDefaultReject(Node* node); - Reduction ReducePromiseCapabilityDefaultResolve(Node* node); Reduction ReducePromiseConstructor(Node* node); Reduction ReducePromiseInternalConstructor(Node* node); Reduction ReducePromiseInternalReject(Node* node); From d4166defe184780c9c4c7e1d18905a61a69b7b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Mon, 16 Jul 2018 22:03:01 +0200 Subject: [PATCH 3/3] deps: cherry-pick 907d7bc from upstream V8 Original commit message: [promise] Implement Swallowed Rejection Hook. This extends the current Promise Rejection Hook with two new events kPromiseRejectAfterResolved kPromiseResolveAfterResolved which are used to detect (and signal) misuse of the Promise constructor. Specifically the common bug like new Promise((res, rej) => { res(1); throw new Error("something") }); where the error is silently swallowed by the Promise constructor without the user ever noticing can be caught via this hook. Doc: https://goo.gl/2stLUY Bug: v8:7919 Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng Change-Id: I890a7e766cdd1be88db94844fb744f72823dba33 Reviewed-on: https://chromium-review.googlesource.com/1126099 Reviewed-by: Maya Lekova Reviewed-by: Benedikt Meurer Reviewed-by: Yang Guo Commit-Queue: Benedikt Meurer Cr-Commit-Position: refs/heads/master@{#54309} Refs: https://github.com/v8/v8/commit/907d7bcd18c13a04a14eea6699e54167494bf9f9 --- common.gypi | 2 +- deps/v8/include/v8.h | 4 +- deps/v8/src/builtins/builtins-promise-gen.cc | 34 +++++- deps/v8/src/builtins/builtins-promise-gen.h | 7 +- deps/v8/src/compiler/js-call-reducer.cc | 4 + deps/v8/src/isolate.cc | 3 +- deps/v8/src/runtime/runtime-promise.cc | 20 ++++ deps/v8/src/runtime/runtime.h | 4 +- deps/v8/test/cctest/test-api.cc | 116 ++++++++++++++----- 9 files changed, 149 insertions(+), 45 deletions(-) diff --git a/common.gypi b/common.gypi index 17948b9da3b7..447ff992b467 100644 --- a/common.gypi +++ b/common.gypi @@ -28,7 +28,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.16', + 'v8_embedder_string': '-node.17', # Enable disassembler for `--print-code` v8 options 'v8_enable_disassembler': 1, diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index fe8f5a383952..389a7c01b058 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -6394,7 +6394,9 @@ typedef void (*PromiseHook)(PromiseHookType type, Local promise, // --- Promise Reject Callback --- enum PromiseRejectEvent { kPromiseRejectWithNoHandler = 0, - kPromiseHandlerAddedAfterReject = 1 + kPromiseHandlerAddedAfterReject = 1, + kPromiseRejectAfterResolved = 2, + kPromiseResolveAfterResolved = 3, }; class PromiseRejectMessage { diff --git a/deps/v8/src/builtins/builtins-promise-gen.cc b/deps/v8/src/builtins/builtins-promise-gen.cc index 868b45a8316f..0a94139fed49 100644 --- a/deps/v8/src/builtins/builtins-promise-gen.cc +++ b/deps/v8/src/builtins/builtins-promise-gen.cc @@ -247,6 +247,8 @@ Node* PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext( Node* const context = CreatePromiseContext(native_context, kPromiseContextLength); StoreContextElementNoWriteBarrier(context, kPromiseSlot, promise); + StoreContextElementNoWriteBarrier(context, kAlreadyResolvedSlot, + FalseConstant()); StoreContextElementNoWriteBarrier(context, kDebugEventSlot, debug_event); return context; } @@ -733,17 +735,27 @@ TF_BUILTIN(PromiseCapabilityDefaultReject, PromiseBuiltinsAssembler) { Node* const promise = LoadContextElement(context, kPromiseSlot); // 3. Let alreadyResolved be F.[[AlreadyResolved]]. + Label if_already_resolved(this, Label::kDeferred); + Node* const already_resolved = + LoadContextElement(context, kAlreadyResolvedSlot); + // 4. If alreadyResolved.[[Value]] is true, return undefined. - // We use undefined as a marker for the [[AlreadyResolved]] state. - ReturnIf(IsUndefined(promise), UndefinedConstant()); + GotoIf(IsTrue(already_resolved), &if_already_resolved); // 5. Set alreadyResolved.[[Value]] to true. - StoreContextElementNoWriteBarrier(context, kPromiseSlot, UndefinedConstant()); + StoreContextElementNoWriteBarrier(context, kAlreadyResolvedSlot, + TrueConstant()); // 6. Return RejectPromise(promise, reason). Node* const debug_event = LoadContextElement(context, kDebugEventSlot); Return(CallBuiltin(Builtins::kRejectPromise, context, promise, reason, debug_event)); + + BIND(&if_already_resolved); + { + Return(CallRuntime(Runtime::kPromiseRejectAfterResolved, context, promise, + reason)); + } } // ES #sec-promise-resolve-functions @@ -755,16 +767,26 @@ TF_BUILTIN(PromiseCapabilityDefaultResolve, PromiseBuiltinsAssembler) { Node* const promise = LoadContextElement(context, kPromiseSlot); // 3. Let alreadyResolved be F.[[AlreadyResolved]]. + Label if_already_resolved(this, Label::kDeferred); + Node* const already_resolved = + LoadContextElement(context, kAlreadyResolvedSlot); + // 4. If alreadyResolved.[[Value]] is true, return undefined. - // We use undefined as a marker for the [[AlreadyResolved]] state. - ReturnIf(IsUndefined(promise), UndefinedConstant()); + GotoIf(IsTrue(already_resolved), &if_already_resolved); // 5. Set alreadyResolved.[[Value]] to true. - StoreContextElementNoWriteBarrier(context, kPromiseSlot, UndefinedConstant()); + StoreContextElementNoWriteBarrier(context, kAlreadyResolvedSlot, + TrueConstant()); // The rest of the logic (and the catch prediction) is // encapsulated in the dedicated ResolvePromise builtin. Return(CallBuiltin(Builtins::kResolvePromise, context, promise, resolution)); + + BIND(&if_already_resolved); + { + Return(CallRuntime(Runtime::kPromiseResolveAfterResolved, context, promise, + resolution)); + } } TF_BUILTIN(PromiseConstructorLazyDeoptContinuation, PromiseBuiltinsAssembler) { diff --git a/deps/v8/src/builtins/builtins-promise-gen.h b/deps/v8/src/builtins/builtins-promise-gen.h index 694cea28c06b..0920c33cf886 100644 --- a/deps/v8/src/builtins/builtins-promise-gen.h +++ b/deps/v8/src/builtins/builtins-promise-gen.h @@ -17,11 +17,12 @@ typedef compiler::CodeAssemblerState CodeAssemblerState; class PromiseBuiltinsAssembler : public CodeStubAssembler { public: enum PromiseResolvingFunctionContextSlot { - // The promise which resolve/reject callbacks fulfill. If this is - // undefined, then we've already visited this callback and it - // should be a no-op. + // The promise which resolve/reject callbacks fulfill. kPromiseSlot = Context::MIN_CONTEXT_SLOTS, + // Whether the callback was already invoked. + kAlreadyResolvedSlot, + // Whether to trigger a debug event or not. Used in catch // prediction. kDebugEventSlot, diff --git a/deps/v8/src/compiler/js-call-reducer.cc b/deps/v8/src/compiler/js-call-reducer.cc index 8e5034cde07d..f226c7bd5dbd 100644 --- a/deps/v8/src/compiler/js-call-reducer.cc +++ b/deps/v8/src/compiler/js-call-reducer.cc @@ -5345,6 +5345,10 @@ Reduction JSCallReducer::ReducePromiseConstructor(Node* node) { graph()->NewNode(simplified()->StoreField(AccessBuilder::ForContextSlot( PromiseBuiltinsAssembler::kPromiseSlot)), promise_context, promise, effect, control); + effect = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForContextSlot( + PromiseBuiltinsAssembler::kAlreadyResolvedSlot)), + promise_context, jsgraph()->FalseConstant(), effect, control); effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForContextSlot( PromiseBuiltinsAssembler::kDebugEventSlot)), diff --git a/deps/v8/src/isolate.cc b/deps/v8/src/isolate.cc index 7ef0214f4a64..849e4ba5be0a 100644 --- a/deps/v8/src/isolate.cc +++ b/deps/v8/src/isolate.cc @@ -3875,10 +3875,9 @@ void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) { void Isolate::ReportPromiseReject(Handle promise, Handle value, v8::PromiseRejectEvent event) { - DCHECK_EQ(v8::Promise::kRejected, promise->status()); if (promise_reject_callback_ == nullptr) return; Handle stack_trace; - if (event == v8::kPromiseRejectWithNoHandler && value->IsJSObject()) { + if (event != v8::kPromiseHandlerAddedAfterReject && value->IsJSObject()) { stack_trace = GetDetailedStackTrace(Handle::cast(value)); } promise_reject_callback_(v8::PromiseRejectMessage( diff --git a/deps/v8/src/runtime/runtime-promise.cc b/deps/v8/src/runtime/runtime-promise.cc index f5b9db3c028b..6c4f7d69eb7d 100644 --- a/deps/v8/src/runtime/runtime-promise.cc +++ b/deps/v8/src/runtime/runtime-promise.cc @@ -38,6 +38,26 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) { return isolate->heap()->undefined_value(); } +RUNTIME_FUNCTION(Runtime_PromiseRejectAfterResolved) { + DCHECK_EQ(2, args.length()); + HandleScope scope(isolate); + CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); + CONVERT_ARG_HANDLE_CHECKED(Object, reason, 1); + isolate->ReportPromiseReject(promise, reason, + v8::kPromiseRejectAfterResolved); + return isolate->heap()->undefined_value(); +} + +RUNTIME_FUNCTION(Runtime_PromiseResolveAfterResolved) { + DCHECK_EQ(2, args.length()); + HandleScope scope(isolate); + CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); + CONVERT_ARG_HANDLE_CHECKED(Object, resolution, 1); + isolate->ReportPromiseReject(promise, resolution, + v8::kPromiseResolveAfterResolved); + return isolate->heap()->undefined_value(); +} + RUNTIME_FUNCTION(Runtime_PromiseRevokeReject) { DCHECK_EQ(1, args.length()); HandleScope scope(isolate); diff --git a/deps/v8/src/runtime/runtime.h b/deps/v8/src/runtime/runtime.h index 37ce74e8250f..184b9c5ca445 100644 --- a/deps/v8/src/runtime/runtime.h +++ b/deps/v8/src/runtime/runtime.h @@ -433,7 +433,9 @@ namespace internal { F(PromiseRevokeReject, 1, 1) \ F(PromiseStatus, 1, 1) \ F(RejectPromise, 3, 1) \ - F(ResolvePromise, 2, 1) + F(ResolvePromise, 2, 1) \ + F(PromiseRejectAfterResolved, 2, 1) \ + F(PromiseResolveAfterResolved, 2, 1) #define FOR_EACH_INTRINSIC_PROXY(F) \ F(CheckProxyGetSetTrapResult, 2, 1) \ diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc index 637dc7d2062f..793711fa80c9 100644 --- a/deps/v8/test/cctest/test-api.cc +++ b/deps/v8/test/cctest/test-api.cc @@ -17625,6 +17625,8 @@ TEST(RethrowBogusErrorStackTrace) { v8::PromiseRejectEvent reject_event = v8::kPromiseRejectWithNoHandler; int promise_reject_counter = 0; int promise_revoke_counter = 0; +int promise_reject_after_resolved_counter = 0; +int promise_resolve_after_resolved_counter = 0; int promise_reject_msg_line_number = -1; int promise_reject_msg_column_number = -1; int promise_reject_line_number = -1; @@ -17634,40 +17636,56 @@ int promise_reject_frame_count = -1; void PromiseRejectCallback(v8::PromiseRejectMessage reject_message) { v8::Local global = CcTest::global(); v8::Local context = CcTest::isolate()->GetCurrentContext(); - CHECK_EQ(v8::Promise::PromiseState::kRejected, + CHECK_NE(v8::Promise::PromiseState::kPending, reject_message.GetPromise()->State()); - if (reject_message.GetEvent() == v8::kPromiseRejectWithNoHandler) { - promise_reject_counter++; - global->Set(context, v8_str("rejected"), reject_message.GetPromise()) - .FromJust(); - global->Set(context, v8_str("value"), reject_message.GetValue()).FromJust(); - v8::Local message = v8::Exception::CreateMessage( - CcTest::isolate(), reject_message.GetValue()); - v8::Local stack_trace = message->GetStackTrace(); - - promise_reject_msg_line_number = message->GetLineNumber(context).FromJust(); - promise_reject_msg_column_number = - message->GetStartColumn(context).FromJust() + 1; - - if (!stack_trace.IsEmpty()) { - promise_reject_frame_count = stack_trace->GetFrameCount(); - if (promise_reject_frame_count > 0) { - CHECK(stack_trace->GetFrame(0) - ->GetScriptName() - ->Equals(context, v8_str("pro")) - .FromJust()); - promise_reject_line_number = stack_trace->GetFrame(0)->GetLineNumber(); - promise_reject_column_number = stack_trace->GetFrame(0)->GetColumn(); - } else { - promise_reject_line_number = -1; - promise_reject_column_number = -1; + switch (reject_message.GetEvent()) { + case v8::kPromiseRejectWithNoHandler: { + promise_reject_counter++; + global->Set(context, v8_str("rejected"), reject_message.GetPromise()) + .FromJust(); + global->Set(context, v8_str("value"), reject_message.GetValue()) + .FromJust(); + v8::Local message = v8::Exception::CreateMessage( + CcTest::isolate(), reject_message.GetValue()); + v8::Local stack_trace = message->GetStackTrace(); + + promise_reject_msg_line_number = + message->GetLineNumber(context).FromJust(); + promise_reject_msg_column_number = + message->GetStartColumn(context).FromJust() + 1; + + if (!stack_trace.IsEmpty()) { + promise_reject_frame_count = stack_trace->GetFrameCount(); + if (promise_reject_frame_count > 0) { + CHECK(stack_trace->GetFrame(0) + ->GetScriptName() + ->Equals(context, v8_str("pro")) + .FromJust()); + promise_reject_line_number = + stack_trace->GetFrame(0)->GetLineNumber(); + promise_reject_column_number = stack_trace->GetFrame(0)->GetColumn(); + } else { + promise_reject_line_number = -1; + promise_reject_column_number = -1; + } } + break; + } + case v8::kPromiseHandlerAddedAfterReject: { + promise_revoke_counter++; + global->Set(context, v8_str("revoked"), reject_message.GetPromise()) + .FromJust(); + CHECK(reject_message.GetValue().IsEmpty()); + break; + } + case v8::kPromiseRejectAfterResolved: { + promise_reject_after_resolved_counter++; + break; + } + case v8::kPromiseResolveAfterResolved: { + promise_resolve_after_resolved_counter++; + break; } - } else { - promise_revoke_counter++; - global->Set(context, v8_str("revoked"), reject_message.GetPromise()) - .FromJust(); - CHECK(reject_message.GetValue().IsEmpty()); } } @@ -17690,6 +17708,8 @@ v8::Local RejectValue() { void ResetPromiseStates() { promise_reject_counter = 0; promise_revoke_counter = 0; + promise_reject_after_resolved_counter = 0; + promise_resolve_after_resolved_counter = 0; promise_reject_msg_line_number = -1; promise_reject_msg_column_number = -1; promise_reject_line_number = -1; @@ -17915,6 +17935,40 @@ TEST(PromiseRejectCallback) { CHECK_EQ(0, promise_revoke_counter); CHECK(RejectValue()->Equals(env.local(), v8_str("sss")).FromJust()); + ResetPromiseStates(); + + // Swallowed exceptions in the Promise constructor. + CompileRun( + "var v0 = new Promise(\n" + " function(res, rej) {\n" + " res(1);\n" + " throw new Error();\n" + " }\n" + ");\n"); + CHECK(!GetPromise("v0")->HasHandler()); + CHECK_EQ(0, promise_reject_counter); + CHECK_EQ(0, promise_revoke_counter); + CHECK_EQ(1, promise_reject_after_resolved_counter); + CHECK_EQ(0, promise_resolve_after_resolved_counter); + + ResetPromiseStates(); + + // Duplication resolve. + CompileRun( + "var r;\n" + "var y0 = new Promise(\n" + " function(res, rej) {\n" + " r = res;\n" + " throw new Error();\n" + " }\n" + ");\n" + "r(1);\n"); + CHECK(!GetPromise("y0")->HasHandler()); + CHECK_EQ(1, promise_reject_counter); + CHECK_EQ(0, promise_revoke_counter); + CHECK_EQ(0, promise_reject_after_resolved_counter); + CHECK_EQ(1, promise_resolve_after_resolved_counter); + // Test stack frames. env->GetIsolate()->SetCaptureStackTraceForUncaughtExceptions(true);