Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

node.cc: break on uncaught exceptions #5713

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 56 additions & 29 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,7 @@ MakeDomainCallback(const Handle<Object> object,
Local<Function> exit;

TryCatch try_catch;
try_catch.SetVerbose(true);

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

if (try_catch.HasCaught()) {
FatalException(try_catch);
return Undefined(node_isolate);
}
}

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

if (try_catch.HasCaught()) {
FatalException(try_catch);
return Undefined(node_isolate);
}

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

if (try_catch.HasCaught()) {
FatalException(try_catch);
return Undefined(node_isolate);
}
}
Expand All @@ -954,7 +952,6 @@ MakeDomainCallback(const Handle<Object> object,
process_tickCallback->Call(process, 0, NULL);

if (try_catch.HasCaught()) {
FatalException(try_catch);
return Undefined(node_isolate);
}

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

TryCatch try_catch;
try_catch.SetVerbose(true);

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

if (try_catch.HasCaught()) {
FatalException(try_catch);
return Undefined(node_isolate);
}

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

if (try_catch.HasCaught()) {
FatalException(try_catch);
return Undefined(node_isolate);
}

Expand Down Expand Up @@ -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> message) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you're here... style: no space between function name and parenthesis.

// 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.
Expand All @@ -1140,10 +1136,6 @@ void DisplayExceptionLine (TryCatch &try_catch) {
if (displayed_error) return;
displayed_error = true;

HandleScope scope(node_isolate);

Handle<Message> message = try_catch.Message();

uv_tty_reset_mode();

fprintf(stderr, "\n");
Expand Down Expand Up @@ -1197,21 +1189,21 @@ void DisplayExceptionLine (TryCatch &try_catch) {
}


static void ReportException(TryCatch &try_catch, bool show_line) {
static void ReportException(Handle<Value> er, Handle<Message> message) {
HandleScope scope(node_isolate);

if (show_line) DisplayExceptionLine(try_catch);
DisplayExceptionLine(message);

String::Utf8Value trace(try_catch.StackTrace());
Local<Value> traceValue(er->ToObject()->Get(String::New("stack")));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We predominantly use snake_case rather than camelCase in C++ code.

String::Utf8Value trace(traceValue);

// range errors have a trace member set to undefined
if (trace.length() > 0 && !try_catch.StackTrace()->IsUndefined()) {
if (trace.length() > 0 && !traceValue->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<Value> er = try_catch.Exception();
bool isErrorObject = er->IsObject() &&
!(er->ToObject()->Get(String::New("message"))->IsUndefined()) &&
!(er->ToObject()->Get(String::New("name"))->IsUndefined());
Expand All @@ -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<Value> ExecuteString(Handle<String> source, Handle<Value> 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<v8::Script> script = v8::Script::Compile(source, filename);
if (script.IsEmpty()) {
ReportException(try_catch, true);
ReportException(try_catch);
exit(3);
}

Local<Value> result = script->Run();
if (result.IsEmpty()) {
ReportException(try_catch, true);
ReportException(try_catch);
exit(4);
}

Expand Down Expand Up @@ -1869,7 +1871,8 @@ static void OnFatalError(const char* location, const char* message) {
exit(5);
}

void FatalException(TryCatch &try_catch) {

void FatalException(Handle<Value> error, Handle<Message> message) {
HandleScope scope(node_isolate);

if (fatal_exception_symbol.IsEmpty())
Expand All @@ -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<Function> fatal_f = Local<Function>::Cast(fatal_v);

Local<Value> error = try_catch.Exception();
Local<Value> argv[] = { error };
Handle<Value> 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<Value> caught = fatal_f->Call(process, ARRAY_SIZE(argv), argv);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could change this to fatal_f->Call(process, 1, &error); and drop argv.


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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: TryCatch& try_catch

HandleScope scope(node_isolate);
// TODO do not call FatalException if try_catch is verbose
FatalException(try_catch.Exception(), try_catch.Message());
}


void OnMessage(Handle<Message> message, Handle<Value> error) {
// TODO - check if exception is set?
FatalException(error, message);
}


Persistent<Object> binding_cache;
Persistent<Array> module_load_list;

Expand Down Expand Up @@ -2416,10 +2434,14 @@ void Load(Handle<Object> process_l) {

TryCatch try_catch;

// try_catch must be not verbose to disable FatalException() handler
// Load exceptions cannot be ignored (handled) by _fatalException
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment reads a bit ambiguous. I assume you mean something like "Disable verbose mode to stop FatalException() from trying to handle the exception. Errors this early in the start-up phase are not safe to ignore."

try_catch.SetVerbose(false);

Local<Value> 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());
Expand All @@ -2445,11 +2467,15 @@ void Load(Handle<Object> 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 debugger fast enought to break on exception
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/debugger/a debugger/ (or 'the debugger')

// thrown during process startup.
try_catch.SetVerbose(true);

if (try_catch.HasCaught()) {
FatalException(try_catch);
}
f->Call(global, 1, args);
}

static void PrintHelp();
Expand Down Expand Up @@ -2936,6 +2962,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) {
Expand Down
2 changes: 1 addition & 1 deletion src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ enum encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER};
enum encoding ParseEncoding(v8::Handle<v8::Value> 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<v8::Message> message);

NODE_EXTERN v8::Local<v8::Value> Encode(const void *buf, size_t len,
enum encoding encoding = BINARY);
Expand Down
14 changes: 12 additions & 2 deletions src/node_script.cc
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ Handle<Value> 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<Value> result;
Handle<Script> script;

Expand All @@ -411,7 +415,10 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
: Script::New(code, filename);
if (script.IsEmpty()) {
// FIXME UGLY HACK TO DISPLAY SYNTAX ERRORS.
if (display_error) DisplayExceptionLine(try_catch);
if (display_error) {
HandleScope scope(node_isolate);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the HandleScope necessary?

DisplayExceptionLine(try_catch.Message());
}

// Hack because I can't get a proper stacktrace on SyntaxError
return try_catch.ReThrow();
Expand Down Expand Up @@ -444,7 +451,10 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
String::New("Script execution timed out.")));
}
if (result.IsEmpty()) {
if (display_error) DisplayExceptionLine(try_catch);
if (display_error) {
HandleScope scope(node_isolate);
DisplayExceptionLine(try_catch.Message());
}
return try_catch.ReThrow();
}
} else {
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/uncaught-exceptions/domain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
var domain = require('domain');

var d = domain.create();
d.on('error', function(err) {
console.log('[ignored]', err.stack);
});

d.run(function() {
setImmediate(function() {
throw new Error('in domain');
});
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing whitespace.

2 changes: 2 additions & 0 deletions test/fixtures/uncaught-exceptions/global.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log('going to throw an error');
throw new Error('global');
2 changes: 2 additions & 0 deletions test/fixtures/uncaught-exceptions/parse-error-mod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log('parse error on next line');
var a = ';
2 changes: 2 additions & 0 deletions test/fixtures/uncaught-exceptions/parse-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log('require fails on next line');
require('./parse-error-mod.js');
3 changes: 3 additions & 0 deletions test/fixtures/uncaught-exceptions/timeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
setTimeout(function() {
throw new Error('timeout');
}, 10);
Loading