Skip to content

Commit c16963b

Browse files
bajtosbnoordhuis
authored andcommitted
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.
1 parent 4bc024d commit c16963b

File tree

9 files changed

+211
-34
lines changed

9 files changed

+211
-34
lines changed

src/node.cc

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,7 @@ MakeDomainCallback(const Handle<Object> object,
908908
Local<Function> exit;
909909

910910
TryCatch try_catch;
911+
try_catch.SetVerbose(true);
911912

912913
bool has_domain = domain_v->IsObject();
913914
if (has_domain) {
@@ -922,15 +923,13 @@ MakeDomainCallback(const Handle<Object> object,
922923
enter->Call(domain, 0, NULL);
923924

924925
if (try_catch.HasCaught()) {
925-
FatalException(try_catch);
926926
return Undefined(node_isolate);
927927
}
928928
}
929929

930930
Local<Value> ret = callback->Call(object, argc, argv);
931931

932932
if (try_catch.HasCaught()) {
933-
FatalException(try_catch);
934933
return Undefined(node_isolate);
935934
}
936935

@@ -940,7 +939,6 @@ MakeDomainCallback(const Handle<Object> object,
940939
exit->Call(domain, 0, NULL);
941940

942941
if (try_catch.HasCaught()) {
943-
FatalException(try_catch);
944942
return Undefined(node_isolate);
945943
}
946944
}
@@ -954,7 +952,6 @@ MakeDomainCallback(const Handle<Object> object,
954952
process_tickCallback->Call(process, 0, NULL);
955953

956954
if (try_catch.HasCaught()) {
957-
FatalException(try_catch);
958955
return Undefined(node_isolate);
959956
}
960957

@@ -981,11 +978,11 @@ MakeCallback(const Handle<Object> object,
981978
}
982979

983980
TryCatch try_catch;
981+
try_catch.SetVerbose(true);
984982

985983
Local<Value> ret = callback->Call(object, argc, argv);
986984

987985
if (try_catch.HasCaught()) {
988-
FatalException(try_catch);
989986
return Undefined(node_isolate);
990987
}
991988

@@ -998,7 +995,6 @@ MakeCallback(const Handle<Object> object,
998995
process_tickCallback->Call(process, 0, NULL);
999996

1000997
if (try_catch.HasCaught()) {
1001-
FatalException(try_catch);
1002998
return Undefined(node_isolate);
1003999
}
10041000

@@ -1131,7 +1127,7 @@ ssize_t DecodeWrite(char *buf,
11311127
return StringBytes::Write(buf, buflen, val, encoding, NULL);
11321128
}
11331129

1134-
void DisplayExceptionLine (TryCatch &try_catch) {
1130+
void DisplayExceptionLine(Handle<Message> message) {
11351131
// Prevent re-entry into this function. For example, if there is
11361132
// a throw from a program in vm.runInThisContext(code, filename, true),
11371133
// then we want to show the original failure, not the secondary one.
@@ -1140,10 +1136,6 @@ void DisplayExceptionLine (TryCatch &try_catch) {
11401136
if (displayed_error) return;
11411137
displayed_error = true;
11421138

1143-
HandleScope scope(node_isolate);
1144-
1145-
Handle<Message> message = try_catch.Message();
1146-
11471139
uv_tty_reset_mode();
11481140

11491141
fprintf(stderr, "\n");
@@ -1197,21 +1189,21 @@ void DisplayExceptionLine (TryCatch &try_catch) {
11971189
}
11981190

11991191

1200-
static void ReportException(TryCatch &try_catch, bool show_line) {
1192+
static void ReportException(Handle<Value> er, Handle<Message> message) {
12011193
HandleScope scope(node_isolate);
12021194

1203-
if (show_line) DisplayExceptionLine(try_catch);
1195+
DisplayExceptionLine(message);
12041196

1205-
String::Utf8Value trace(try_catch.StackTrace());
1197+
Local<Value> trace_value(er->ToObject()->Get(String::New("stack")));
1198+
String::Utf8Value trace(trace_value);
12061199

12071200
// range errors have a trace member set to undefined
1208-
if (trace.length() > 0 && !try_catch.StackTrace()->IsUndefined()) {
1201+
if (trace.length() > 0 && !trace_value->IsUndefined()) {
12091202
fprintf(stderr, "%s\n", *trace);
12101203
} else {
12111204
// this really only happens for RangeErrors, since they're the only
12121205
// kind that won't have all this info in the trace, or when non-Error
12131206
// objects are thrown manually.
1214-
Local<Value> er = try_catch.Exception();
12151207
bool isErrorObject = er->IsObject() &&
12161208
!(er->ToObject()->Get(String::New("message"))->IsUndefined()) &&
12171209
!(er->ToObject()->Get(String::New("name"))->IsUndefined());
@@ -1229,20 +1221,30 @@ static void ReportException(TryCatch &try_catch, bool show_line) {
12291221
fflush(stderr);
12301222
}
12311223

1224+
1225+
static void ReportException(TryCatch& try_catch) {
1226+
ReportException(try_catch.Exception(), try_catch.Message());
1227+
}
1228+
1229+
12321230
// Executes a str within the current v8 context.
12331231
Local<Value> ExecuteString(Handle<String> source, Handle<Value> filename) {
12341232
HandleScope scope(node_isolate);
12351233
TryCatch try_catch;
12361234

1235+
// try_catch must be nonverbose to disable FatalException() handler,
1236+
// we will handle exceptions ourself.
1237+
try_catch.SetVerbose(false);
1238+
12371239
Local<v8::Script> script = v8::Script::Compile(source, filename);
12381240
if (script.IsEmpty()) {
1239-
ReportException(try_catch, true);
1241+
ReportException(try_catch);
12401242
exit(3);
12411243
}
12421244

12431245
Local<Value> result = script->Run();
12441246
if (result.IsEmpty()) {
1245-
ReportException(try_catch, true);
1247+
ReportException(try_catch);
12461248
exit(4);
12471249
}
12481250

@@ -1869,7 +1871,8 @@ static void OnFatalError(const char* location, const char* message) {
18691871
exit(5);
18701872
}
18711873

1872-
void FatalException(TryCatch &try_catch) {
1874+
1875+
void FatalException(Handle<Value> error, Handle<Message> message) {
18731876
HandleScope scope(node_isolate);
18741877

18751878
if (fatal_exception_symbol.IsEmpty())
@@ -1880,33 +1883,48 @@ void FatalException(TryCatch &try_catch) {
18801883
if (!fatal_v->IsFunction()) {
18811884
// failed before the process._fatalException function was added!
18821885
// this is probably pretty bad. Nothing to do but report and exit.
1883-
ReportException(try_catch, true);
1886+
ReportException(error, message);
18841887
exit(6);
18851888
}
18861889

18871890
Local<Function> fatal_f = Local<Function>::Cast(fatal_v);
18881891

1889-
Local<Value> error = try_catch.Exception();
1890-
Local<Value> argv[] = { error };
1891-
18921892
TryCatch fatal_try_catch;
18931893

1894+
// Do not call FatalException when _fatalException handler throws
1895+
fatal_try_catch.SetVerbose(false);
1896+
18941897
// this will return true if the JS layer handled it, false otherwise
1895-
Local<Value> caught = fatal_f->Call(process, ARRAY_SIZE(argv), argv);
1898+
Local<Value> caught = fatal_f->Call(process, 1, &error);
18961899

18971900
if (fatal_try_catch.HasCaught()) {
18981901
// the fatal exception function threw, so we must exit
1899-
ReportException(fatal_try_catch, true);
1902+
ReportException(fatal_try_catch);
19001903
exit(7);
19011904
}
19021905

19031906
if (false == caught->BooleanValue()) {
1904-
ReportException(try_catch, true);
1907+
ReportException(error, message);
19051908
exit(8);
19061909
}
19071910
}
19081911

19091912

1913+
void FatalException(TryCatch& try_catch) {
1914+
HandleScope scope(node_isolate);
1915+
// TODO do not call FatalException if try_catch is verbose
1916+
// (requires V8 API to expose getter for try_catch.is_verbose_)
1917+
FatalException(try_catch.Exception(), try_catch.Message());
1918+
}
1919+
1920+
1921+
void OnMessage(Handle<Message> message, Handle<Value> error) {
1922+
// The current version of V8 sends messages for errors only
1923+
// (thus `error` is always set).
1924+
FatalException(error, message);
1925+
}
1926+
1927+
19101928
Persistent<Object> binding_cache;
19111929
Persistent<Array> module_load_list;
19121930

@@ -2416,10 +2434,15 @@ void Load(Handle<Object> process_l) {
24162434

24172435
TryCatch try_catch;
24182436

2437+
// Disable verbose mode to stop FatalException() handler from trying
2438+
// to handle the exception. Errors this early in the start-up phase
2439+
// are not safe to ignore.
2440+
try_catch.SetVerbose(false);
2441+
24192442
Local<Value> f_value = ExecuteString(MainSource(),
24202443
IMMUTABLE_STRING("node.js"));
24212444
if (try_catch.HasCaught()) {
2422-
ReportException(try_catch, true);
2445+
ReportException(try_catch);
24232446
exit(10);
24242447
}
24252448
assert(f_value->IsFunction());
@@ -2445,11 +2468,15 @@ void Load(Handle<Object> process_l) {
24452468
InitPerfCounters(global);
24462469
#endif
24472470

2448-
f->Call(global, 1, args);
2471+
// Enable handling of uncaught exceptions
2472+
// (FatalException(), break on uncaught exception in debugger)
2473+
//
2474+
// This is not strictly necessary since it's almost impossible
2475+
// to attach the debugger fast enought to break on exception
2476+
// thrown during process startup.
2477+
try_catch.SetVerbose(true);
24492478

2450-
if (try_catch.HasCaught()) {
2451-
FatalException(try_catch);
2452-
}
2479+
f->Call(global, 1, args);
24532480
}
24542481

24552482
static void PrintHelp();
@@ -2936,6 +2963,7 @@ char** Init(int argc, char *argv[]) {
29362963
uv_idle_init(uv_default_loop(), &idle_immediate_dummy);
29372964

29382965
V8::SetFatalErrorHandler(node::OnFatalError);
2966+
V8::AddMessageListener(OnMessage);
29392967

29402968
// If the --debug flag was specified then initialize the debug thread.
29412969
if (use_debug_agent) {

src/node.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ enum encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER};
133133
enum encoding ParseEncoding(v8::Handle<v8::Value> encoding_v,
134134
enum encoding _default = BINARY);
135135
NODE_EXTERN void FatalException(v8::TryCatch &try_catch);
136-
void DisplayExceptionLine(v8::TryCatch &try_catch); // hack
136+
void DisplayExceptionLine(v8::Handle<v8::Message> message);
137137

138138
NODE_EXTERN v8::Local<v8::Value> Encode(const void *buf, size_t len,
139139
enum encoding encoding = BINARY);

src/node_script.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,10 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
401401
// Catch errors
402402
TryCatch try_catch;
403403

404+
// TryCatch must not be verbose to prevent duplicate logging
405+
// of uncaught exceptions (we are rethrowing them)
406+
try_catch.SetVerbose(false);
407+
404408
Handle<Value> result;
405409
Handle<Script> script;
406410

@@ -411,7 +415,7 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
411415
: Script::New(code, filename);
412416
if (script.IsEmpty()) {
413417
// FIXME UGLY HACK TO DISPLAY SYNTAX ERRORS.
414-
if (display_error) DisplayExceptionLine(try_catch);
418+
if (display_error) DisplayExceptionLine(try_catch.Message());
415419

416420
// Hack because I can't get a proper stacktrace on SyntaxError
417421
return try_catch.ReThrow();
@@ -444,7 +448,7 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
444448
String::New("Script execution timed out.")));
445449
}
446450
if (result.IsEmpty()) {
447-
if (display_error) DisplayExceptionLine(try_catch);
451+
if (display_error) DisplayExceptionLine(try_catch.Message());
448452
return try_catch.ReThrow();
449453
}
450454
} else {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
var domain = require('domain');
2+
3+
var d = domain.create();
4+
d.on('error', function(err) {
5+
console.log('[ignored]', err.stack);
6+
});
7+
8+
d.run(function() {
9+
setImmediate(function() {
10+
throw new Error('in domain');
11+
});
12+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
console.log('going to throw an error');
2+
throw new Error('global');
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
console.log('parse error on next line');
2+
var a = ';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
console.log('require fails on next line');
2+
require('./parse-error-mod.js');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
setTimeout(function() {
2+
throw new Error('timeout');
3+
}, 10);

0 commit comments

Comments
 (0)