From c16963b977b3cede4cef476c275226a513831407 Mon Sep 17 00:00:00 2001 From: Miroslav Bajtos Date: Mon, 17 Jun 2013 21:19:59 +0200 Subject: [PATCH] src: break on uncaught exception Most TryCatch blocks have SetVerbose flag on, this tells V8 to report uncaught exceptions to debugger. FatalException handler is called from V8 Message listener instead from the place where TryCatch was used. Otherwise uncaught exceptions are logged twice. See comment in `deps/v8/include/v8.h` for explanation of SetVerbose flag: > By default, exceptions that are caught by an external exception > handler are not reported. Call SetVerbose with true on an > external exception handler to have exceptions caught by the > handler reported as if they were not caught. The flag is used by `Isolate::ShouldReportException()`, which is called by `Isolate::DoThrow()` to decide whether an exception is considered uncaught. --- src/node.cc | 90 ++++++++----- src/node.h | 2 +- src/node_script.cc | 8 +- test/fixtures/uncaught-exceptions/domain.js | 12 ++ test/fixtures/uncaught-exceptions/global.js | 2 + .../uncaught-exceptions/parse-error-mod.js | 2 + .../uncaught-exceptions/parse-error.js | 2 + test/fixtures/uncaught-exceptions/timeout.js | 3 + test/simple/test-debug-break-on-uncaught.js | 124 ++++++++++++++++++ 9 files changed, 211 insertions(+), 34 deletions(-) create mode 100644 test/fixtures/uncaught-exceptions/domain.js create mode 100644 test/fixtures/uncaught-exceptions/global.js create mode 100644 test/fixtures/uncaught-exceptions/parse-error-mod.js create mode 100644 test/fixtures/uncaught-exceptions/parse-error.js create mode 100644 test/fixtures/uncaught-exceptions/timeout.js create mode 100644 test/simple/test-debug-break-on-uncaught.js diff --git a/src/node.cc b/src/node.cc index 6aa0eda69ddb4a..38ecbb836c78f8 100644 --- a/src/node.cc +++ b/src/node.cc @@ -908,6 +908,7 @@ MakeDomainCallback(const Handle object, Local exit; TryCatch try_catch; + try_catch.SetVerbose(true); bool has_domain = domain_v->IsObject(); if (has_domain) { @@ -922,7 +923,6 @@ MakeDomainCallback(const Handle object, enter->Call(domain, 0, NULL); if (try_catch.HasCaught()) { - FatalException(try_catch); return Undefined(node_isolate); } } @@ -930,7 +930,6 @@ MakeDomainCallback(const Handle object, Local ret = callback->Call(object, argc, argv); if (try_catch.HasCaught()) { - FatalException(try_catch); return Undefined(node_isolate); } @@ -940,7 +939,6 @@ MakeDomainCallback(const Handle object, exit->Call(domain, 0, NULL); if (try_catch.HasCaught()) { - FatalException(try_catch); return Undefined(node_isolate); } } @@ -954,7 +952,6 @@ MakeDomainCallback(const Handle object, process_tickCallback->Call(process, 0, NULL); if (try_catch.HasCaught()) { - FatalException(try_catch); return Undefined(node_isolate); } @@ -981,11 +978,11 @@ MakeCallback(const Handle object, } TryCatch try_catch; + try_catch.SetVerbose(true); Local ret = callback->Call(object, argc, argv); if (try_catch.HasCaught()) { - FatalException(try_catch); return Undefined(node_isolate); } @@ -998,7 +995,6 @@ MakeCallback(const Handle object, process_tickCallback->Call(process, 0, NULL); if (try_catch.HasCaught()) { - FatalException(try_catch); return Undefined(node_isolate); } @@ -1131,7 +1127,7 @@ ssize_t DecodeWrite(char *buf, return StringBytes::Write(buf, buflen, val, encoding, NULL); } -void DisplayExceptionLine (TryCatch &try_catch) { +void DisplayExceptionLine(Handle message) { // Prevent re-entry into this function. For example, if there is // a throw from a program in vm.runInThisContext(code, filename, true), // then we want to show the original failure, not the secondary one. @@ -1140,10 +1136,6 @@ void DisplayExceptionLine (TryCatch &try_catch) { if (displayed_error) return; displayed_error = true; - HandleScope scope(node_isolate); - - Handle message = try_catch.Message(); - uv_tty_reset_mode(); fprintf(stderr, "\n"); @@ -1197,21 +1189,21 @@ void DisplayExceptionLine (TryCatch &try_catch) { } -static void ReportException(TryCatch &try_catch, bool show_line) { +static void ReportException(Handle er, Handle message) { HandleScope scope(node_isolate); - if (show_line) DisplayExceptionLine(try_catch); + DisplayExceptionLine(message); - String::Utf8Value trace(try_catch.StackTrace()); + Local trace_value(er->ToObject()->Get(String::New("stack"))); + String::Utf8Value trace(trace_value); // range errors have a trace member set to undefined - if (trace.length() > 0 && !try_catch.StackTrace()->IsUndefined()) { + if (trace.length() > 0 && !trace_value->IsUndefined()) { fprintf(stderr, "%s\n", *trace); } else { // this really only happens for RangeErrors, since they're the only // kind that won't have all this info in the trace, or when non-Error // objects are thrown manually. - Local er = try_catch.Exception(); bool isErrorObject = er->IsObject() && !(er->ToObject()->Get(String::New("message"))->IsUndefined()) && !(er->ToObject()->Get(String::New("name"))->IsUndefined()); @@ -1229,20 +1221,30 @@ static void ReportException(TryCatch &try_catch, bool show_line) { fflush(stderr); } + +static void ReportException(TryCatch& try_catch) { + ReportException(try_catch.Exception(), try_catch.Message()); +} + + // Executes a str within the current v8 context. Local ExecuteString(Handle source, Handle filename) { HandleScope scope(node_isolate); TryCatch try_catch; + // try_catch must be nonverbose to disable FatalException() handler, + // we will handle exceptions ourself. + try_catch.SetVerbose(false); + Local script = v8::Script::Compile(source, filename); if (script.IsEmpty()) { - ReportException(try_catch, true); + ReportException(try_catch); exit(3); } Local result = script->Run(); if (result.IsEmpty()) { - ReportException(try_catch, true); + ReportException(try_catch); exit(4); } @@ -1869,7 +1871,8 @@ static void OnFatalError(const char* location, const char* message) { exit(5); } -void FatalException(TryCatch &try_catch) { + +void FatalException(Handle error, Handle message) { HandleScope scope(node_isolate); if (fatal_exception_symbol.IsEmpty()) @@ -1880,33 +1883,48 @@ void FatalException(TryCatch &try_catch) { if (!fatal_v->IsFunction()) { // failed before the process._fatalException function was added! // this is probably pretty bad. Nothing to do but report and exit. - ReportException(try_catch, true); + ReportException(error, message); exit(6); } Local fatal_f = Local::Cast(fatal_v); - Local error = try_catch.Exception(); - Local argv[] = { error }; - TryCatch fatal_try_catch; + // Do not call FatalException when _fatalException handler throws + fatal_try_catch.SetVerbose(false); + // this will return true if the JS layer handled it, false otherwise - Local caught = fatal_f->Call(process, ARRAY_SIZE(argv), argv); + Local caught = fatal_f->Call(process, 1, &error); if (fatal_try_catch.HasCaught()) { // the fatal exception function threw, so we must exit - ReportException(fatal_try_catch, true); + ReportException(fatal_try_catch); exit(7); } if (false == caught->BooleanValue()) { - ReportException(try_catch, true); + ReportException(error, message); exit(8); } } +void FatalException(TryCatch& try_catch) { + HandleScope scope(node_isolate); + // TODO do not call FatalException if try_catch is verbose + // (requires V8 API to expose getter for try_catch.is_verbose_) + FatalException(try_catch.Exception(), try_catch.Message()); +} + + +void OnMessage(Handle message, Handle error) { + // The current version of V8 sends messages for errors only + // (thus `error` is always set). + FatalException(error, message); +} + + Persistent binding_cache; Persistent module_load_list; @@ -2416,10 +2434,15 @@ void Load(Handle process_l) { TryCatch try_catch; + // Disable verbose mode to stop FatalException() handler from trying + // to handle the exception. Errors this early in the start-up phase + // are not safe to ignore. + try_catch.SetVerbose(false); + Local f_value = ExecuteString(MainSource(), IMMUTABLE_STRING("node.js")); if (try_catch.HasCaught()) { - ReportException(try_catch, true); + ReportException(try_catch); exit(10); } assert(f_value->IsFunction()); @@ -2445,11 +2468,15 @@ void Load(Handle process_l) { InitPerfCounters(global); #endif - f->Call(global, 1, args); + // Enable handling of uncaught exceptions + // (FatalException(), break on uncaught exception in debugger) + // + // This is not strictly necessary since it's almost impossible + // to attach the debugger fast enought to break on exception + // thrown during process startup. + try_catch.SetVerbose(true); - if (try_catch.HasCaught()) { - FatalException(try_catch); - } + f->Call(global, 1, args); } static void PrintHelp(); @@ -2936,6 +2963,7 @@ char** Init(int argc, char *argv[]) { uv_idle_init(uv_default_loop(), &idle_immediate_dummy); V8::SetFatalErrorHandler(node::OnFatalError); + V8::AddMessageListener(OnMessage); // If the --debug flag was specified then initialize the debug thread. if (use_debug_agent) { diff --git a/src/node.h b/src/node.h index 49f342fae360b4..59d03a355aef81 100644 --- a/src/node.h +++ b/src/node.h @@ -133,7 +133,7 @@ enum encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER}; enum encoding ParseEncoding(v8::Handle encoding_v, enum encoding _default = BINARY); NODE_EXTERN void FatalException(v8::TryCatch &try_catch); -void DisplayExceptionLine(v8::TryCatch &try_catch); // hack +void DisplayExceptionLine(v8::Handle message); NODE_EXTERN v8::Local Encode(const void *buf, size_t len, enum encoding encoding = BINARY); diff --git a/src/node_script.cc b/src/node_script.cc index 639f41ab01ee81..4766cd05a69863 100644 --- a/src/node_script.cc +++ b/src/node_script.cc @@ -401,6 +401,10 @@ Handle WrappedScript::EvalMachine(const Arguments& args) { // Catch errors TryCatch try_catch; + // TryCatch must not be verbose to prevent duplicate logging + // of uncaught exceptions (we are rethrowing them) + try_catch.SetVerbose(false); + Handle result; Handle