Commit 767e65f9 authored by Gus Caplan's avatar Gus Caplan Committed by Commit Bot

[API] Fix microtask message reporting

RunSingleMicrotask calls Runtime::ReportMessage, but the implementation
of ReportMessage would unconditionally discard these exceptions. This
CL removes all of the intermediate logic and directly calls
MessageHandler::ReportMessage, restoring the ability of
RunSingleMicrotask to report exceptions that occur in microtasks.

Bug: v8:8326
Change-Id: I493de74383b2ab191d786611fb9eba9d27e7a243
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2162121
Commit-Queue: Gus Caplan <me@gus.host>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67630}
parent e87972b1
...@@ -320,7 +320,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( ...@@ -320,7 +320,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
BIND(&if_exception); BIND(&if_exception);
{ {
// Report unhandled exceptions from microtasks. // Report unhandled exceptions from microtasks.
CallRuntime(Runtime::kReportMessage, current_context, CallRuntime(Runtime::kReportMessageFromMicrotask, current_context,
var_exception.value()); var_exception.value());
RewindEnteredContext(saved_entered_context_count); RewindEnteredContext(saved_entered_context_count);
SetCurrentContext(current_context); SetCurrentContext(current_context);
......
...@@ -4,11 +4,6 @@ ...@@ -4,11 +4,6 @@
#include 'src/builtins/builtins-promise-gen.h' #include 'src/builtins/builtins-promise-gen.h'
namespace runtime {
extern transitioning runtime
ReportMessage(implicit context: Context)(JSAny): JSAny;
}
namespace promise { namespace promise {
transitioning transitioning
...@@ -30,13 +25,8 @@ namespace promise { ...@@ -30,13 +25,8 @@ namespace promise {
case (capability: PromiseCapability): { case (capability: PromiseCapability): {
// In the general case we need to call the (user provided) // In the general case we need to call the (user provided)
// promiseCapability.[[Reject]] function. // promiseCapability.[[Reject]] function.
try { const reject = UnsafeCast<Callable>(capability.reject);
const reject = UnsafeCast<Callable>(capability.reject); return Call(context, reject, Undefined, reason);
return Call(context, reject, Undefined, reason);
} catch (e) {
// Swallow the exception here.
return runtime::ReportMessage(e);
}
} }
} }
} else { } else {
......
...@@ -1397,6 +1397,8 @@ void Isolate::InvokeApiInterruptCallbacks() { ...@@ -1397,6 +1397,8 @@ void Isolate::InvokeApiInterruptCallbacks() {
} }
} }
namespace {
void ReportBootstrappingException(Handle<Object> exception, void ReportBootstrappingException(Handle<Object> exception,
MessageLocation* location) { MessageLocation* location) {
base::OS::PrintError("Exception thrown during bootstrapping\n"); base::OS::PrintError("Exception thrown during bootstrapping\n");
...@@ -1452,6 +1454,36 @@ void ReportBootstrappingException(Handle<Object> exception, ...@@ -1452,6 +1454,36 @@ void ReportBootstrappingException(Handle<Object> exception,
#endif #endif
} }
} // anonymous namespace
Handle<JSMessageObject> Isolate::CreateMessageOrAbort(
Handle<Object> exception, MessageLocation* location) {
Handle<JSMessageObject> message_obj = CreateMessage(exception, location);
// If the abort-on-uncaught-exception flag is specified, and if the
// embedder didn't specify a custom uncaught exception callback,
// or if the custom callback determined that V8 should abort, then
// abort.
if (FLAG_abort_on_uncaught_exception) {
CatchType prediction = PredictExceptionCatcher();
if ((prediction == NOT_CAUGHT || prediction == CAUGHT_BY_EXTERNAL) &&
(!abort_on_uncaught_exception_callback_ ||
abort_on_uncaught_exception_callback_(
reinterpret_cast<v8::Isolate*>(this)))) {
// Prevent endless recursion.
FLAG_abort_on_uncaught_exception = false;
// This flag is intended for use by JavaScript developers, so
// print a user-friendly stack trace (not an internal one).
PrintF(stderr, "%s\n\nFROM\n",
MessageHandler::GetLocalizedMessage(this, message_obj).get());
PrintCurrentStackTrace(stderr);
base::OS::Abort();
}
}
return message_obj;
}
Object Isolate::Throw(Object raw_exception, MessageLocation* location) { Object Isolate::Throw(Object raw_exception, MessageLocation* location) {
DCHECK(!has_pending_exception()); DCHECK(!has_pending_exception());
...@@ -1523,38 +1555,14 @@ Object Isolate::Throw(Object raw_exception, MessageLocation* location) { ...@@ -1523,38 +1555,14 @@ Object Isolate::Throw(Object raw_exception, MessageLocation* location) {
if (location == nullptr && ComputeLocation(&computed_location)) { if (location == nullptr && ComputeLocation(&computed_location)) {
location = &computed_location; location = &computed_location;
} }
if (bootstrapper()->IsActive()) { if (bootstrapper()->IsActive()) {
// It's not safe to try to make message objects or collect stack traces // It's not safe to try to make message objects or collect stack traces
// while the bootstrapper is active since the infrastructure may not have // while the bootstrapper is active since the infrastructure may not have
// been properly initialized. // been properly initialized.
ReportBootstrappingException(exception, location); ReportBootstrappingException(exception, location);
} else { } else {
Handle<Object> message_obj = CreateMessage(exception, location); Handle<Object> message_obj = CreateMessageOrAbort(exception, location);
thread_local_top()->pending_message_obj_ = *message_obj; thread_local_top()->pending_message_obj_ = *message_obj;
// For any exception not caught by JavaScript, even when an external
// handler is present:
// If the abort-on-uncaught-exception flag is specified, and if the
// embedder didn't specify a custom uncaught exception callback,
// or if the custom callback determined that V8 should abort, then
// abort.
if (FLAG_abort_on_uncaught_exception) {
CatchType prediction = PredictExceptionCatcher();
if ((prediction == NOT_CAUGHT || prediction == CAUGHT_BY_EXTERNAL) &&
(!abort_on_uncaught_exception_callback_ ||
abort_on_uncaught_exception_callback_(
reinterpret_cast<v8::Isolate*>(this)))) {
// Prevent endless recursion.
FLAG_abort_on_uncaught_exception = false;
// This flag is intended for use by JavaScript developers, so
// print a user-friendly stack trace (not an internal one).
PrintF(stderr, "%s\n\nFROM\n",
MessageHandler::GetLocalizedMessage(this, message_obj).get());
PrintCurrentStackTrace(stderr);
base::OS::Abort();
}
}
} }
} }
...@@ -2085,7 +2093,7 @@ bool Isolate::ComputeLocationFromStackTrace(MessageLocation* target, ...@@ -2085,7 +2093,7 @@ bool Isolate::ComputeLocationFromStackTrace(MessageLocation* target,
bool is_at_number_conversion = bool is_at_number_conversion =
elements->IsAsmJsWasmFrame(i) && elements->IsAsmJsWasmFrame(i) &&
elements->Flags(i).value() & FrameArray::kAsmJsAtNumberConversion; elements->Flags(i).value() & FrameArray::kAsmJsAtNumberConversion;
if (elements->IsWasmCompiledFrame(i)) { if (elements->IsWasmCompiledFrame(i) || elements->IsAsmJsWasmFrame(i)) {
// WasmCode* held alive by the {GlobalWasmCodeRef}. // WasmCode* held alive by the {GlobalWasmCodeRef}.
wasm::WasmCode* code = wasm::WasmCode* code =
Managed<wasm::GlobalWasmCodeRef>::cast(elements->WasmCodeObject(i)) Managed<wasm::GlobalWasmCodeRef>::cast(elements->WasmCodeObject(i))
...@@ -2208,9 +2216,28 @@ bool Isolate::IsExternalHandlerOnTop(Object exception) { ...@@ -2208,9 +2216,28 @@ bool Isolate::IsExternalHandlerOnTop(Object exception) {
return (entry_handler > external_handler); return (entry_handler > external_handler);
} }
void Isolate::ReportPendingMessagesImpl(bool report_externally) { std::vector<MemoryRange>* Isolate::GetCodePages() const {
return code_pages_.load(std::memory_order_acquire);
}
void Isolate::SetCodePages(std::vector<MemoryRange>* new_code_pages) {
code_pages_.store(new_code_pages, std::memory_order_release);
}
void Isolate::ReportPendingMessages() {
DCHECK(AllowExceptions::IsAllowed(this));
// The embedder might run script in response to an exception.
AllowJavascriptExecutionDebugOnly allow_script(this);
Object exception_obj = pending_exception(); Object exception_obj = pending_exception();
// Try to propagate the exception to an external v8::TryCatch handler. If
// propagation was unsuccessful, then we will get another chance at reporting
// the pending message if the exception is re-thrown.
bool has_been_propagated = PropagatePendingExceptionToExternalTryCatch();
if (!has_been_propagated) return;
// Clear the pending message object early to avoid endless recursion. // Clear the pending message object early to avoid endless recursion.
Object message_obj = thread_local_top()->pending_message_obj_; Object message_obj = thread_local_top()->pending_message_obj_;
clear_pending_message(); clear_pending_message();
...@@ -2223,7 +2250,7 @@ void Isolate::ReportPendingMessagesImpl(bool report_externally) { ...@@ -2223,7 +2250,7 @@ void Isolate::ReportPendingMessagesImpl(bool report_externally) {
// depending on whether and external v8::TryCatch or an internal JavaScript // depending on whether and external v8::TryCatch or an internal JavaScript
// handler is on top. // handler is on top.
bool should_report_exception; bool should_report_exception;
if (report_externally) { if (IsExternalHandlerOnTop(exception_obj)) {
// Only report the exception if the external handler is verbose. // Only report the exception if the external handler is verbose.
should_report_exception = try_catch_handler()->is_verbose_; should_report_exception = try_catch_handler()->is_verbose_;
} else { } else {
...@@ -2249,93 +2276,6 @@ void Isolate::ReportPendingMessagesImpl(bool report_externally) { ...@@ -2249,93 +2276,6 @@ void Isolate::ReportPendingMessagesImpl(bool report_externally) {
} }
} }
std::vector<MemoryRange>* Isolate::GetCodePages() const {
return code_pages_.load(std::memory_order_acquire);
}
void Isolate::SetCodePages(std::vector<MemoryRange>* new_code_pages) {
code_pages_.store(new_code_pages, std::memory_order_release);
}
void Isolate::ReportPendingMessages() {
DCHECK(AllowExceptions::IsAllowed(this));
// The embedder might run script in response to an exception.
AllowJavascriptExecutionDebugOnly allow_script(this);
Object exception = pending_exception();
// Try to propagate the exception to an external v8::TryCatch handler. If
// propagation was unsuccessful, then we will get another chance at reporting
// the pending message if the exception is re-thrown.
bool has_been_propagated = PropagatePendingExceptionToExternalTryCatch();
if (!has_been_propagated) return;
ReportPendingMessagesImpl(IsExternalHandlerOnTop(exception));
}
void Isolate::ReportPendingMessagesFromJavaScript() {
DCHECK(AllowExceptions::IsAllowed(this));
auto IsHandledByJavaScript = [=]() {
// In this situation, the exception is always a non-terminating exception.
// Get the top-most JS_ENTRY handler, cannot be on top if it doesn't exist.
Address entry_handler = Isolate::handler(thread_local_top());
DCHECK_NE(entry_handler, kNullAddress);
entry_handler = StackHandler::FromAddress(entry_handler)->next_address();
// Get the address of the external handler so we can compare the address to
// determine which one is closer to the top of the stack.
Address external_handler = thread_local_top()->try_catch_handler_address();
if (external_handler == kNullAddress) return true;
return (entry_handler < external_handler);
};
auto IsHandledExternally = [=]() {
Address external_handler = thread_local_top()->try_catch_handler_address();
if (external_handler == kNullAddress) return false;
// Get the top-most JS_ENTRY handler, cannot be on top if it doesn't exist.
Address entry_handler = Isolate::handler(thread_local_top());
DCHECK_NE(entry_handler, kNullAddress);
entry_handler = StackHandler::FromAddress(entry_handler)->next_address();
return (entry_handler > external_handler);
};
auto PropagateToExternalHandler = [=]() {
if (IsHandledByJavaScript()) {
thread_local_top()->external_caught_exception_ = false;
return false;
}
if (!IsHandledExternally()) {
thread_local_top()->external_caught_exception_ = false;
return true;
}
thread_local_top()->external_caught_exception_ = true;
v8::TryCatch* handler = try_catch_handler();
DCHECK(thread_local_top()->pending_message_obj_.IsJSMessageObject() ||
thread_local_top()->pending_message_obj_.IsTheHole(this));
handler->can_continue_ = true;
handler->has_terminated_ = false;
handler->exception_ = reinterpret_cast<void*>(pending_exception().ptr());
// Propagate to the external try-catch only if we got an actual message.
if (thread_local_top()->pending_message_obj_.IsTheHole(this)) return true;
handler->message_obj_ =
reinterpret_cast<void*>(thread_local_top()->pending_message_obj_.ptr());
return true;
};
// Try to propagate to an external v8::TryCatch handler.
if (!PropagateToExternalHandler()) return;
ReportPendingMessagesImpl(true);
}
bool Isolate::OptionalRescheduleException(bool clear_exception) { bool Isolate::OptionalRescheduleException(bool clear_exception) {
DCHECK(has_pending_exception()); DCHECK(has_pending_exception());
PropagatePendingExceptionToExternalTryCatch(); PropagatePendingExceptionToExternalTryCatch();
......
...@@ -825,10 +825,6 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { ...@@ -825,10 +825,6 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
// Un-schedule an exception that was caught by a TryCatch handler. // Un-schedule an exception that was caught by a TryCatch handler.
void CancelScheduledExceptionFromTryCatch(v8::TryCatch* handler); void CancelScheduledExceptionFromTryCatch(v8::TryCatch* handler);
void ReportPendingMessages(); void ReportPendingMessages();
void ReportPendingMessagesFromJavaScript();
// Implements code shared between the two above methods
void ReportPendingMessagesImpl(bool report_externally);
// Promote a scheduled exception to pending. Asserts has_scheduled_exception. // Promote a scheduled exception to pending. Asserts has_scheduled_exception.
Object PromoteScheduledException(); Object PromoteScheduledException();
...@@ -845,6 +841,8 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { ...@@ -845,6 +841,8 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
Handle<JSMessageObject> CreateMessage(Handle<Object> exception, Handle<JSMessageObject> CreateMessage(Handle<Object> exception,
MessageLocation* location); MessageLocation* location);
Handle<JSMessageObject> CreateMessageOrAbort(Handle<Object> exception,
MessageLocation* location);
// Out of resource exception helpers. // Out of resource exception helpers.
Object StackOverflow(); Object StackOverflow();
......
...@@ -598,19 +598,21 @@ RUNTIME_FUNCTION(Runtime_GetTemplateObject) { ...@@ -598,19 +598,21 @@ RUNTIME_FUNCTION(Runtime_GetTemplateObject) {
isolate, native_context, description, shared_info, slot_id); isolate, native_context, description, shared_info, slot_id);
} }
RUNTIME_FUNCTION(Runtime_ReportMessage) { RUNTIME_FUNCTION(Runtime_ReportMessageFromMicrotask) {
// Helper to report messages and continue JS execution. This is intended to // Helper to report messages and continue JS execution. This is intended to
// behave similarly to reporting exceptions which reach the top-level in // behave similarly to reporting exceptions which reach the top-level, but
// Execution.cc, but allow the JS code to continue. This is useful for // allow the JS code to continue.
// implementing algorithms such as RunMicrotasks in JS.
HandleScope scope(isolate); HandleScope scope(isolate);
DCHECK_EQ(1, args.length()); DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, message_obj, 0); CONVERT_ARG_HANDLE_CHECKED(Object, exception, 0);
DCHECK(!isolate->has_pending_exception()); DCHECK(!isolate->has_pending_exception());
isolate->set_pending_exception(*message_obj); isolate->set_pending_exception(*exception);
isolate->ReportPendingMessagesFromJavaScript(); MessageLocation* no_location = nullptr;
Handle<JSMessageObject> message =
isolate->CreateMessageOrAbort(exception, no_location);
MessageHandler::ReportMessage(isolate, no_location, message);
isolate->clear_pending_exception(); isolate->clear_pending_exception();
return ReadOnlyRoots(isolate).undefined_value(); return ReadOnlyRoots(isolate).undefined_value();
} }
......
...@@ -225,7 +225,7 @@ namespace internal { ...@@ -225,7 +225,7 @@ namespace internal {
F(NewTypeError, -1 /* [1, 4] */, 1) \ F(NewTypeError, -1 /* [1, 4] */, 1) \
F(OrdinaryHasInstance, 2, 1) \ F(OrdinaryHasInstance, 2, 1) \
F(PromoteScheduledException, 0, 1) \ F(PromoteScheduledException, 0, 1) \
F(ReportMessage, 1, 1) \ F(ReportMessageFromMicrotask, 1, 1) \
F(ReThrow, 1, 1) \ F(ReThrow, 1, 1) \
F(RunMicrotaskCallback, 2, 1) \ F(RunMicrotaskCallback, 2, 1) \
F(PerformMicrotaskCheckpoint, 0, 1) \ F(PerformMicrotaskCheckpoint, 0, 1) \
......
...@@ -19829,11 +19829,30 @@ static void MicrotaskExceptionTwo( ...@@ -19829,11 +19829,30 @@ static void MicrotaskExceptionTwo(
v8::Exception::Error(v8_str("second"))); v8::Exception::Error(v8_str("second")));
} }
int handler_call_count = 0;
static void MicrotaskExceptionHandler(Local<Message> message,
Local<Value> exception) {
CHECK(exception->IsNativeError());
Local<Context> context = message->GetIsolate()->GetCurrentContext();
Local<String> str = exception->ToString(context).ToLocalChecked();
switch (handler_call_count++) {
case 0:
CHECK(str->StrictEquals(v8_str("Error: first")));
break;
case 1:
CHECK(str->StrictEquals(v8_str("Error: second")));
break;
default:
UNREACHABLE();
}
}
TEST(RunMicrotasksIgnoresThrownExceptions) { TEST(RunMicrotasksIgnoresThrownExceptions) {
LocalContext env; LocalContext env;
v8::Isolate* isolate = env->GetIsolate(); v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate); v8::HandleScope scope(isolate);
isolate->AddMessageListenerWithErrorLevel(MicrotaskExceptionHandler,
v8::Isolate::kMessageAll);
CompileRun( CompileRun(
"var exception1Calls = 0;" "var exception1Calls = 0;"
"var exception2Calls = 0;"); "var exception2Calls = 0;");
...@@ -19844,6 +19863,7 @@ TEST(RunMicrotasksIgnoresThrownExceptions) { ...@@ -19844,6 +19863,7 @@ TEST(RunMicrotasksIgnoresThrownExceptions) {
TryCatch try_catch(isolate); TryCatch try_catch(isolate);
CompileRun("1+1;"); CompileRun("1+1;");
CHECK(!try_catch.HasCaught()); CHECK(!try_catch.HasCaught());
CHECK_EQ(handler_call_count, 2);
CHECK_EQ(1, CHECK_EQ(1,
CompileRun("exception1Calls")->Int32Value(env.local()).FromJust()); CompileRun("exception1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(1, CHECK_EQ(1,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment