Skip to content

Commit

Permalink
cli: add --trace-uncaught flag
Browse files Browse the repository at this point in the history
Add a flag that makes Node.js print the stack trace at the
time of *throwing* uncaught exceptions, rather than at the
creation of the `Error` object, if there is any.

This is disabled by default because it affects GC behavior.

PR-URL: #30025
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
  • Loading branch information
addaleax authored and BethGriggs committed Feb 6, 2020
1 parent b560f7b commit dc58731
Show file tree
Hide file tree
Showing 17 changed files with 111 additions and 0 deletions.
13 changes: 13 additions & 0 deletions doc/api/cli.md
Expand Up @@ -785,6 +785,18 @@ added: v12.2.0
Prints TLS packet trace information to `stderr`. This can be used to debug TLS
connection problems.

### `--trace-uncaught`
<!-- YAML
added: REPLACEME
-->

Print stack traces for uncaught exceptions; usually, the stack trace associated
with the creation of an `Error` is printed, whereas this makes Node.js also
print the stack trace associated with throwing the value (which does not need
to be an `Error` instance).

Enabling this option may affect garbage collection behavior negatively.

### `--trace-warnings`
<!-- YAML
added: v6.0.0
Expand Down Expand Up @@ -1100,6 +1112,7 @@ Node.js options that are allowed are:
* `--trace-events-enabled`
* `--trace-sync-io`
* `--trace-tls`
* `--trace-uncaught`
* `--trace-warnings`
* `--track-heap-objects`
* `--unhandled-rejections`
Expand Down
12 changes: 12 additions & 0 deletions doc/node.1
Expand Up @@ -352,6 +352,18 @@ Print a stack trace whenever synchronous I/O is detected after the first turn of
.It Fl -trace-tls
Prints TLS packet trace information to stderr.
.
.It Fl -trace-uncaught
Print stack traces for uncaught exceptions; usually, the stack trace associated
with the creation of an
.Sy Error
is printed, whereas this makes Node.js also
print the stack trace associated with throwing the value (which does not need
to be an
.Sy Error
instance).
.Pp
Enabling this option may affect garbage collection behavior negatively.
.
.It Fl -trace-warnings
Print stack traces for process warnings (including deprecations).
.
Expand Down
2 changes: 2 additions & 0 deletions src/node.cc
Expand Up @@ -232,6 +232,8 @@ int Environment::InitializeInspector(
void Environment::InitializeDiagnostics() {
isolate_->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
Environment::BuildEmbedderGraph, this);
if (options_->trace_uncaught)
isolate_->SetCaptureStackTraceForUncaughtExceptions(true);

#if defined HAVE_DTRACE || defined HAVE_ETW
InitDTrace(this);
Expand Down
13 changes: 13 additions & 0 deletions src/node_errors.cc
Expand Up @@ -380,6 +380,19 @@ static void ReportFatalException(Environment* env,
"%s\n%s: %s\n", *arrow_string, *name_string, *message_string);
}
}

if (!env->options()->trace_uncaught) {
PrintErrorString("(Use `node --trace-uncaught ...` to show "
"where the exception was thrown)\n");
}
}

if (env->options()->trace_uncaught) {
Local<StackTrace> trace = message->GetStackTrace();
if (!trace.IsEmpty()) {
PrintErrorString("Thrown at:\n");
PrintStackTrace(env->isolate(), trace);
}
}

fflush(stderr);
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Expand Up @@ -498,6 +498,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"prints TLS packet trace information to stderr",
&EnvironmentOptions::trace_tls,
kAllowedInEnvironment);
AddOption("--trace-uncaught",
"show stack traces for the `throw` behind uncaught exceptions",
&EnvironmentOptions::trace_uncaught,
kAllowedInEnvironment);
AddOption("--trace-warnings",
"show stack traces on process warnings",
&EnvironmentOptions::trace_warnings,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Expand Up @@ -142,6 +142,7 @@ class EnvironmentOptions : public Options {
bool trace_deprecation = false;
bool trace_sync_io = false;
bool trace_tls = false;
bool trace_uncaught = false;
bool trace_warnings = false;
std::string unhandled_rejections;
std::string userland_loader;
Expand Down
2 changes: 2 additions & 0 deletions test/message/eval_messages.out
Expand Up @@ -55,9 +55,11 @@ ReferenceError: y is not defined
var ______________________________________________; throw 10
^
10
(Use `node --trace-uncaught ...` to show where the exception was thrown)

[eval]:1
var ______________________________________________; throw 10
^
10
(Use `node --trace-uncaught ...` to show where the exception was thrown)
done
2 changes: 2 additions & 0 deletions test/message/stdin_messages.out
Expand Up @@ -67,9 +67,11 @@ ReferenceError: y is not defined
let ______________________________________________; throw 10
^
10
(Use `node --trace-uncaught ...` to show where the exception was thrown)

[stdin]:1
let ______________________________________________; throw 10
^
10
(Use `node --trace-uncaught ...` to show where the exception was thrown)
done
1 change: 1 addition & 0 deletions test/message/throw_error_with_getter_throw.out
Expand Up @@ -3,3 +3,4 @@
throw { // eslint-disable-line no-throw-literal
^
[object Object]
(Use `node --trace-uncaught ...` to show where the exception was thrown)
11 changes: 11 additions & 0 deletions test/message/throw_error_with_getter_throw_traced.js
@@ -0,0 +1,11 @@
// Flags: --trace-uncaught
'use strict';
require('../common');
throw { // eslint-disable-line no-throw-literal
get stack() {
throw new Error('weird throw but ok');
},
get name() {
throw new Error('weird throw but ok');
},
};
12 changes: 12 additions & 0 deletions test/message/throw_error_with_getter_throw_traced.out
@@ -0,0 +1,12 @@

*:4
throw { // eslint-disable-line no-throw-literal
^
[object Object]
Thrown at:
at *throw_error_with_getter_throw_traced.js:*:*
at Module._compile (internal/modules/cjs/loader.js:*:*)
at Module._extensions..js (internal/modules/cjs/loader.js:*:*)
at Module.load (internal/modules/cjs/loader.js:*:*)
at Module._load (internal/modules/cjs/loader.js:*:*)
at Module.runMain (internal/modules/cjs/loader.js:*:*)
1 change: 1 addition & 0 deletions test/message/throw_null.out
Expand Up @@ -3,3 +3,4 @@
throw null;
^
null
(Use `node --trace-uncaught ...` to show where the exception was thrown)
6 changes: 6 additions & 0 deletions test/message/throw_null_traced.js
@@ -0,0 +1,6 @@
// Flags: --trace-uncaught
'use strict';
require('../common');

// eslint-disable-next-line no-throw-literal
throw null;
12 changes: 12 additions & 0 deletions test/message/throw_null_traced.out
@@ -0,0 +1,12 @@

*test*message*throw_null_traced.js:*
throw null;
^
null
Thrown at:
at *throw_null_traced.js:*:*
at Module._compile (internal/modules/cjs/loader.js:*:*)
at Module._extensions..js (internal/modules/cjs/loader.js:*:*)
at Module.load (internal/modules/cjs/loader.js:*:*)
at Module._load (internal/modules/cjs/loader.js:*:*)
at Module.runMain (internal/modules/cjs/loader.js:*:*)
1 change: 1 addition & 0 deletions test/message/throw_undefined.out
Expand Up @@ -3,3 +3,4 @@
throw undefined;
^
undefined
(Use `node --trace-uncaught ...` to show where the exception was thrown)
6 changes: 6 additions & 0 deletions test/message/throw_undefined_traced.js
@@ -0,0 +1,6 @@
// Flags: --trace-uncaught
'use strict';
require('../common');

// eslint-disable-next-line no-throw-literal
throw undefined;
12 changes: 12 additions & 0 deletions test/message/throw_undefined_traced.out
@@ -0,0 +1,12 @@

*test*message*throw_undefined_traced.js:*
throw undefined;
^
undefined
Thrown at:
at *throw_undefined_traced.js:*:*
at Module._compile (internal/modules/cjs/loader.js:*:*)
at Module._extensions..js (internal/modules/cjs/loader.js:*:*)
at Module.load (internal/modules/cjs/loader.js:*:*)
at Module._load (internal/modules/cjs/loader.js:*:*)
at Module.runMain (internal/modules/cjs/loader.js:*:*)

0 comments on commit dc58731

Please sign in to comment.