Commit 1ee712ab authored by julien.gilli's avatar julien.gilli Committed by Commit bot

Add SetAbortOnUncaughtExceptionCallback API

The --abort-on-uncaught-exception command line switch makes
Isolate::Throw abort if the error being thrown cannot be caught by a
try/catch block.

Embedders may want to use other mechanisms than try/catch blocks to
handle uncaught exceptions. For instance, Node.js has "domain" objects
that have error handlers that can handle uncaught exception like
following:

var d = domain.create();

d.on('error', function onError(err) {
  console.log('Handling error');
});

d.run(function() {
  throw new Error("boom");
});

These error handlers are called by isolates' message listeners.

If --abort-on-uncaught-exception is *not* used, the isolate's
message listener will be called, which will in turn call the domain's
error handler. The process will output 'Handling error' and will exit
successfully (not due to an uncaught exception). This is the behavior
that Node.js users expect.

However, if --abort-on-uncaught-exception is used and when throwing an
error within a domain that has an error handler, the process will abort
and the domain's error handler will not be called. This is not the
behavior that Node.js users expect.

Having a SetAbortOnUncaughtExceptionCallback API allows embedders to
determine when it's not appropriate to abort and instead handle the
exception via the isolate's message listener.

In the example above, Node.js would set a custom callback with
SetAbortOnUncaughtExceptionCallback that would be implemented as
following (the sample code has been simplified to remove what's not
relevant to this change):

bool ShouldAbortOnUncaughtException(Isolate* isolate) {
  return !IsDomainActive();
}

Now when --abort-on-uncaught-exception is used, Isolate::Throw would
call that callback and determine that it should not abort if a domain
with an error handler is active. Instead, the isolate's message listener
would be called and the error would be handled by the domain's error
handler.

I believe this can also be useful for other embedders.

BUG=

R=bmeurer@chromium.org

Review URL: https://codereview.chromium.org/1375933003

Cr-Commit-Position: refs/heads/master@{#31111}
parent e89226a5
...@@ -5377,6 +5377,19 @@ class V8_EXPORT Isolate { ...@@ -5377,6 +5377,19 @@ class V8_EXPORT Isolate {
*/ */
static Isolate* GetCurrent(); static Isolate* GetCurrent();
/**
* Custom callback used by embedders to help V8 determine if it should abort
* when it throws and no internal handler is predicted to catch the
* exception. If --abort-on-uncaught-exception is used on the command line,
* then V8 will abort if either:
* - no custom callback is set.
* - the custom callback set returns true.
* Otherwise, the custom callback will not be called and V8 will not abort.
*/
typedef bool (*AbortOnUncaughtExceptionCallback)(Isolate*);
void SetAbortOnUncaughtExceptionCallback(
AbortOnUncaughtExceptionCallback callback);
/** /**
* Methods below this point require holding a lock (using Locker) in * Methods below this point require holding a lock (using Locker) in
* a multi-threaded environment. * a multi-threaded environment.
......
...@@ -7096,6 +7096,13 @@ void Isolate::Exit() { ...@@ -7096,6 +7096,13 @@ void Isolate::Exit() {
} }
void Isolate::SetAbortOnUncaughtExceptionCallback(
AbortOnUncaughtExceptionCallback callback) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->SetAbortOnUncaughtExceptionCallback(callback);
}
Isolate::DisallowJavascriptExecutionScope::DisallowJavascriptExecutionScope( Isolate::DisallowJavascriptExecutionScope::DisallowJavascriptExecutionScope(
Isolate* isolate, Isolate* isolate,
Isolate::DisallowJavascriptExecutionScope::OnFailure on_failure) Isolate::DisallowJavascriptExecutionScope::OnFailure on_failure)
......
...@@ -1008,13 +1008,21 @@ Object* Isolate::Throw(Object* exception, MessageLocation* location) { ...@@ -1008,13 +1008,21 @@ Object* Isolate::Throw(Object* exception, MessageLocation* location) {
Handle<Object> message_obj = CreateMessage(exception_handle, location); Handle<Object> message_obj = CreateMessage(exception_handle, location);
thread_local_top()->pending_message_obj_ = *message_obj; thread_local_top()->pending_message_obj_ = *message_obj;
// If the abort-on-uncaught-exception flag is specified, abort on any // For any exception not caught by JavaScript, even when an external
// exception not caught by JavaScript, even when an external handler is // handler is present:
// present. This flag is intended for use by JavaScript developers, so // If the abort-on-uncaught-exception flag is specified, and if the
// print a user-friendly stack trace (not an internal one). // 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 && if (FLAG_abort_on_uncaught_exception &&
PredictExceptionCatcher() != CAUGHT_BY_JAVASCRIPT) { PredictExceptionCatcher() != CAUGHT_BY_JAVASCRIPT &&
FLAG_abort_on_uncaught_exception = false; // Prevent endless recursion. (!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", PrintF(stderr, "%s\n\nFROM\n",
MessageHandler::GetLocalizedMessage(this, message_obj).get()); MessageHandler::GetLocalizedMessage(this, message_obj).get());
PrintCurrentStackTrace(stderr); PrintCurrentStackTrace(stderr);
...@@ -1602,6 +1610,12 @@ void Isolate::SetCaptureStackTraceForUncaughtExceptions( ...@@ -1602,6 +1610,12 @@ void Isolate::SetCaptureStackTraceForUncaughtExceptions(
} }
void Isolate::SetAbortOnUncaughtExceptionCallback(
v8::Isolate::AbortOnUncaughtExceptionCallback callback) {
abort_on_uncaught_exception_callback_ = callback;
}
Handle<Context> Isolate::native_context() { Handle<Context> Isolate::native_context() {
return handle(context()->native_context()); return handle(context()->native_context());
} }
...@@ -1770,7 +1784,8 @@ Isolate::Isolate(bool enable_serializer) ...@@ -1770,7 +1784,8 @@ Isolate::Isolate(bool enable_serializer)
next_unique_sfi_id_(0), next_unique_sfi_id_(0),
#endif #endif
use_counter_callback_(NULL), use_counter_callback_(NULL),
basic_block_profiler_(NULL) { basic_block_profiler_(NULL),
abort_on_uncaught_exception_callback_(NULL) {
{ {
base::LockGuard<base::Mutex> lock_guard(thread_data_table_mutex_.Pointer()); base::LockGuard<base::Mutex> lock_guard(thread_data_table_mutex_.Pointer());
CHECK(thread_data_table_); CHECK(thread_data_table_);
......
...@@ -655,6 +655,9 @@ class Isolate { ...@@ -655,6 +655,9 @@ class Isolate {
int frame_limit, int frame_limit,
StackTrace::StackTraceOptions options); StackTrace::StackTraceOptions options);
void SetAbortOnUncaughtExceptionCallback(
v8::Isolate::AbortOnUncaughtExceptionCallback callback);
enum PrintStackMode { kPrintStackConcise, kPrintStackVerbose }; enum PrintStackMode { kPrintStackConcise, kPrintStackVerbose };
void PrintCurrentStackTrace(FILE* out); void PrintCurrentStackTrace(FILE* out);
void PrintStack(StringStream* accumulator, void PrintStack(StringStream* accumulator,
...@@ -1325,6 +1328,9 @@ class Isolate { ...@@ -1325,6 +1328,9 @@ class Isolate {
std::set<Cancelable*> cancelable_tasks_; std::set<Cancelable*> cancelable_tasks_;
v8::Isolate::AbortOnUncaughtExceptionCallback
abort_on_uncaught_exception_callback_;
friend class ExecutionAccess; friend class ExecutionAccess;
friend class HandleScopeImplementer; friend class HandleScopeImplementer;
friend class OptimizingCompileDispatcher; friend class OptimizingCompileDispatcher;
......
...@@ -21865,3 +21865,34 @@ TEST(EstimatedContextSize) { ...@@ -21865,3 +21865,34 @@ TEST(EstimatedContextSize) {
LocalContext env; LocalContext env;
CHECK(50000 < env->EstimatedSize()); CHECK(50000 < env->EstimatedSize());
} }
static int nb_uncaught_exception_callback_calls = 0;
bool NoAbortOnUncaughtException(v8::Isolate* isolate) {
++nb_uncaught_exception_callback_calls;
return false;
}
TEST(AbortOnUncaughtExceptionNoAbort) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope handle_scope(isolate);
v8::Handle<v8::ObjectTemplate> global_template =
v8::ObjectTemplate::New(isolate);
LocalContext env(NULL, global_template);
i::FLAG_abort_on_uncaught_exception = true;
isolate->SetAbortOnUncaughtExceptionCallback(NoAbortOnUncaughtException);
CompileRun("function boom() { throw new Error(\"boom\") }");
v8::Local<v8::Object> global_object = env->Global();
v8::Local<v8::Function> foo =
v8::Local<v8::Function>::Cast(global_object->Get(v8_str("boom")));
foo->Call(global_object, 0, NULL);
CHECK_EQ(1, nb_uncaught_exception_callback_calls);
}
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