From 807b1e55333971148bda2a7905ec977dad8428ff Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Fri, 26 Aug 2022 06:09:05 +0000 Subject: [PATCH] report: get stack trace with cross origin contexts When a new context with a different security token is entered, or when no context is entered, `StackTrace::CurrentStackTrace` need to be explicitly set with flag `kExposeFramesAcrossSecurityOrigins` to avoid crashing. PR-URL: https://github.com/nodejs/node/pull/44398 Reviewed-By: Rafael Gonzaga --- src/node_errors.cc | 5 +++++ src/node_report.cc | 6 +++++- test/addons/report-api/binding.cc | 27 +++++++++++++++++++++++++++ test/addons/report-api/test.js | 31 ++++++++++++++++++++----------- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/node_errors.cc b/src/node_errors.cc index aa54bc2fe7cc4e..323fc7d4ff635c 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -513,6 +513,11 @@ void OOMErrorHandler(const char* location, bool is_heap_oom) { } if (report_on_fatalerror) { + // Trigger report with the isolate. Environment::GetCurrent may return + // nullptr here: + // - If the OOM is reported by a young generation space allocation, + // Isolate::GetCurrentContext returns an empty handle. + // - Otherwise, Isolate::GetCurrentContext returns a non-empty handle. TriggerNodeReport(isolate, message, "OOMError", "", Local()); } diff --git a/src/node_report.cc b/src/node_report.cc index 38391300cda130..bf37e24d09cbfb 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -470,8 +470,12 @@ static void PrintJavaScriptStack(JSONWriter* writer, void* samples[MAX_FRAME_COUNT]; isolate->GetStackSample(state, samples, MAX_FRAME_COUNT, &info); + constexpr StackTrace::StackTraceOptions stack_trace_options = + static_cast( + StackTrace::kDetailed | + StackTrace::kExposeFramesAcrossSecurityOrigins); Local stack = StackTrace::CurrentStackTrace( - isolate, MAX_FRAME_COUNT, StackTrace::kDetailed); + isolate, MAX_FRAME_COUNT, stack_trace_options); if (stack->GetFrameCount() == 0) { PrintEmptyJavaScriptStack(writer); diff --git a/test/addons/report-api/binding.cc b/test/addons/report-api/binding.cc index f52da3c765d7df..4c6a39c7797ac6 100644 --- a/test/addons/report-api/binding.cc +++ b/test/addons/report-api/binding.cc @@ -1,6 +1,7 @@ #include #include +using v8::Context; using v8::FunctionCallbackInfo; using v8::Isolate; using v8::Local; @@ -43,11 +44,37 @@ void TriggerReportNoEnv(const FunctionCallbackInfo& args) { Local()); } +void TriggerReportNoContext(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + context->Exit(); + + if (isolate->GetCurrentContext().IsEmpty()) { + node::TriggerNodeReport( + isolate, "FooMessage", "BarTrigger", std::string(), Local()); + } + + // Restore current context to avoid crashing in Context::Scope in + // SpinEventLoop. + context->Enter(); +} + +void TriggerReportNewContext(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = Context::New(isolate); + Context::Scope context_scope(context); + + node::TriggerNodeReport( + isolate, "FooMessage", "BarTrigger", std::string(), Local()); +} + void init(Local exports) { NODE_SET_METHOD(exports, "triggerReport", TriggerReport); NODE_SET_METHOD(exports, "triggerReportNoIsolate", TriggerReportNoIsolate); NODE_SET_METHOD(exports, "triggerReportEnv", TriggerReportEnv); NODE_SET_METHOD(exports, "triggerReportNoEnv", TriggerReportNoEnv); + NODE_SET_METHOD(exports, "triggerReportNoContext", TriggerReportNoContext); + NODE_SET_METHOD(exports, "triggerReportNewContext", TriggerReportNewContext); } NODE_MODULE(NODE_GYP_MODULE_NAME, init) diff --git a/test/addons/report-api/test.js b/test/addons/report-api/test.js index e5000f56a584e1..67c53404688840 100644 --- a/test/addons/report-api/test.js +++ b/test/addons/report-api/test.js @@ -9,7 +9,7 @@ const tmpdir = require('../../common/tmpdir'); const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`); const addon = require(binding); -function myAddonMain(method, hasJavaScriptFrames) { +function myAddonMain(method, { hasIsolate, hasEnv }) { tmpdir.refresh(); process.report.directory = tmpdir.path; @@ -19,26 +19,35 @@ function myAddonMain(method, hasJavaScriptFrames) { assert.strictEqual(reports.length, 1); const report = reports[0]; - helper.validate(report); + helper.validate(report, [ + ['header.event', 'FooMessage'], + ['header.trigger', 'BarTrigger'], + ]); const content = require(report); - assert.strictEqual(content.header.event, 'FooMessage'); - assert.strictEqual(content.header.trigger, 'BarTrigger'); // Check that the javascript stack is present. - if (hasJavaScriptFrames) { + if (hasIsolate) { assert.strictEqual(content.javascriptStack.stack.findIndex((frame) => frame.match('myAddonMain')), 0); } else { assert.strictEqual(content.javascriptStack, undefined); } + + if (hasEnv) { + assert.strictEqual(content.header.threadId, 0); + } else { + assert.strictEqual(content.header.threadId, null); + } } const methods = [ - ['triggerReport', true], - ['triggerReportNoIsolate', false], - ['triggerReportEnv', true], - ['triggerReportNoEnv', false], + ['triggerReport', true, true], + ['triggerReportNoIsolate', false, false], + ['triggerReportEnv', true, true], + ['triggerReportNoEnv', false, false], + ['triggerReportNoContext', true, false], + ['triggerReportNewContext', true, false], ]; -for (const [method, hasJavaScriptFrames] of methods) { - myAddonMain(method, hasJavaScriptFrames); +for (const [method, hasIsolate, hasEnv] of methods) { + myAddonMain(method, { hasIsolate, hasEnv }); }