diff --git a/src/env-inl.h b/src/env-inl.h index 8fdcf2938054291..4f5fd036e15aedc 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -433,6 +433,10 @@ inline double Environment::get_default_trigger_async_id() { return default_trigger_async_id; } +inline int64_t Environment::stack_trace_limit() const { + return isolate_data_->options()->stack_trace_limit; +} + inline std::shared_ptr Environment::options() { return options_; } diff --git a/src/env.cc b/src/env.cc index 196cffbea4e0344..f9d3cd8878345f7 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1241,9 +1241,11 @@ void Environment::PrintSyncTrace() const { fprintf( stderr, "(node:%d) WARNING: Detected use of sync API\n", uv_os_getpid()); - PrintStackTrace(isolate(), - StackTrace::CurrentStackTrace( - isolate(), stack_trace_limit(), StackTrace::kDetailed)); + PrintStackTrace( + isolate(), + StackTrace::CurrentStackTrace(isolate(), + static_cast(stack_trace_limit()), + StackTrace::kDetailed)); } MaybeLocal Environment::RunSnapshotSerializeCallback() const { @@ -1845,9 +1847,11 @@ void Environment::Exit(ExitCode exit_code) { fprintf(stderr, "WARNING: Exited the environment with code %d\n", static_cast(exit_code)); - PrintStackTrace(isolate(), - StackTrace::CurrentStackTrace( - isolate(), stack_trace_limit(), StackTrace::kDetailed)); + PrintStackTrace( + isolate(), + StackTrace::CurrentStackTrace(isolate(), + static_cast(stack_trace_limit()), + StackTrace::kDetailed)); } process_exit_handler_(this, exit_code); } diff --git a/src/env.h b/src/env.h index 118ccf16cdeed16..02747ccf26e839c 100644 --- a/src/env.h +++ b/src/env.h @@ -977,7 +977,7 @@ class Environment final : public MemoryRetainer { inline std::shared_ptr options(); inline std::shared_ptr> inspector_host_port(); - inline int32_t stack_trace_limit() const { return 10; } + inline int64_t stack_trace_limit() const; #if HAVE_INSPECTOR void set_coverage_connection( diff --git a/src/node_options-inl.h b/src/node_options-inl.h index fabf3be149be93d..24954e0b5838343 100644 --- a/src/node_options-inl.h +++ b/src/node_options-inl.h @@ -447,9 +447,14 @@ void OptionsParser::Parse( case kBoolean: *Lookup(info.field, options) = !is_negation; break; - case kInteger: + case kInteger: { + // Special case to pass --stack-trace-limit down to V8. + if (name == "--stack-trace-limit") { + v8_args->push_back(arg); + } *Lookup(info.field, options) = std::atoll(value.c_str()); break; + } case kUInteger: *Lookup(info.field, options) = std::strtoull(value.c_str(), nullptr, 10); diff --git a/src/node_options.cc b/src/node_options.cc index 6611b86b9f6dcaf..3bfab2759b18f40 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -904,7 +904,10 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( "--perf-basic-prof-only-functions", "", V8Option{}, kAllowedInEnvvar); AddOption("--perf-prof", "", V8Option{}, kAllowedInEnvvar); AddOption("--perf-prof-unwinding-info", "", V8Option{}, kAllowedInEnvvar); - AddOption("--stack-trace-limit", "", V8Option{}, kAllowedInEnvvar); + AddOption("--stack-trace-limit", + "", + &PerIsolateOptions::stack_trace_limit, + kAllowedInEnvvar); AddOption("--disallow-code-generation-from-strings", "disallow eval and friends", V8Option{}, @@ -1291,6 +1294,11 @@ void GetCLIOptionsValues(const FunctionCallbackInfo& args) { if (item.first == "--abort-on-uncaught-exception") { value = Boolean::New(isolate, s.original_per_env->abort_on_uncaught_exception); + } else if (item.first == "--stack-trace-limit") { + value = + Number::New(isolate, + static_cast( + *_ppop_instance.Lookup(field, opts))); } else { value = undefined_value; } diff --git a/src/node_options.h b/src/node_options.h index 83c78b39a286e9e..ed89f08f3b41407 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -271,6 +271,7 @@ class PerIsolateOptions : public Options { bool report_uncaught_exception = false; bool report_on_signal = false; bool experimental_shadow_realm = false; + int64_t stack_trace_limit = 10; std::string report_signal = "SIGUSR2"; bool build_snapshot = false; std::string build_snapshot_config; diff --git a/test/fixtures/deep-exit.js b/test/fixtures/deep-exit.js new file mode 100644 index 000000000000000..357137a279c5562 --- /dev/null +++ b/test/fixtures/deep-exit.js @@ -0,0 +1,15 @@ +'use strict'; + +// This is meant to be run with --trace-exit. + +const depth = parseInt(process.env.STACK_DEPTH) || 30; +let counter = 1; +function recurse() { + if (counter++ < depth) { + recurse(); + } else { + process.exit(0); + } +} + +recurse(); diff --git a/test/parallel/test-trace-exit-stack-limit.js b/test/parallel/test-trace-exit-stack-limit.js new file mode 100644 index 000000000000000..c937ad828fc0322 --- /dev/null +++ b/test/parallel/test-trace-exit-stack-limit.js @@ -0,0 +1,42 @@ +'use strict'; + +// This tests that --stack-trace-limit can be used to tweak the stack trace size of --trace-exit. +require('../common'); +const fixture = require('../common/fixtures'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const assert = require('assert'); + +// When the stack trace limit is bigger than the stack trace size, it should output them all. +spawnSyncAndAssert( + process.execPath, + ['--trace-exit', '--stack-trace-limit=50', fixture.path('deep-exit.js')], + { + env: { + ...process.env, + STACK_DEPTH: 30 + } + }, + { + stderr(output) { + const matches = [...output.matchAll(/at recurse/g)]; + assert.strictEqual(matches.length, 30); + } + }); + +// When the stack trace limit is smaller than the stack trace size, it should truncate the stack size. +spawnSyncAndAssert( + process.execPath, + ['--trace-exit', '--stack-trace-limit=30', fixture.path('deep-exit.js')], + { + env: { + ...process.env, + STACK_DEPTH: 30 + } + }, + { + stderr(output) { + const matches = [...output.matchAll(/at recurse/g)]; + // The top frame is process.exit(), so one frame from recurse() is truncated. + assert.strictEqual(matches.length, 29); + } + });