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 <rafael.nunu@hotmail.com>
This commit is contained in:
Chengzhong Wu 2022-08-26 06:09:05 +00:00 committed by legendecas
parent 7f496fefb6
commit f7896d4671
4 changed files with 57 additions and 12 deletions

View File

@ -513,6 +513,11 @@ void OOMErrorHandler(const char* location, bool is_heap_oom) {
} }
if (report_on_fatalerror) { 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<Object>()); TriggerNodeReport(isolate, message, "OOMError", "", Local<Object>());
} }

View File

@ -470,8 +470,12 @@ static void PrintJavaScriptStack(JSONWriter* writer,
void* samples[MAX_FRAME_COUNT]; void* samples[MAX_FRAME_COUNT];
isolate->GetStackSample(state, samples, MAX_FRAME_COUNT, &info); isolate->GetStackSample(state, samples, MAX_FRAME_COUNT, &info);
constexpr StackTrace::StackTraceOptions stack_trace_options =
static_cast<StackTrace::StackTraceOptions>(
StackTrace::kDetailed |
StackTrace::kExposeFramesAcrossSecurityOrigins);
Local<StackTrace> stack = StackTrace::CurrentStackTrace( Local<StackTrace> stack = StackTrace::CurrentStackTrace(
isolate, MAX_FRAME_COUNT, StackTrace::kDetailed); isolate, MAX_FRAME_COUNT, stack_trace_options);
if (stack->GetFrameCount() == 0) { if (stack->GetFrameCount() == 0) {
PrintEmptyJavaScriptStack(writer); PrintEmptyJavaScriptStack(writer);

View File

@ -1,6 +1,7 @@
#include <node.h> #include <node.h>
#include <v8.h> #include <v8.h>
using v8::Context;
using v8::FunctionCallbackInfo; using v8::FunctionCallbackInfo;
using v8::Isolate; using v8::Isolate;
using v8::Local; using v8::Local;
@ -43,11 +44,37 @@ void TriggerReportNoEnv(const FunctionCallbackInfo<Value>& args) {
Local<Value>()); Local<Value>());
} }
void TriggerReportNoContext(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
context->Exit();
if (isolate->GetCurrentContext().IsEmpty()) {
node::TriggerNodeReport(
isolate, "FooMessage", "BarTrigger", std::string(), Local<Value>());
}
// Restore current context to avoid crashing in Context::Scope in
// SpinEventLoop.
context->Enter();
}
void TriggerReportNewContext(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);
node::TriggerNodeReport(
isolate, "FooMessage", "BarTrigger", std::string(), Local<Value>());
}
void init(Local<Object> exports) { void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "triggerReport", TriggerReport); NODE_SET_METHOD(exports, "triggerReport", TriggerReport);
NODE_SET_METHOD(exports, "triggerReportNoIsolate", TriggerReportNoIsolate); NODE_SET_METHOD(exports, "triggerReportNoIsolate", TriggerReportNoIsolate);
NODE_SET_METHOD(exports, "triggerReportEnv", TriggerReportEnv); NODE_SET_METHOD(exports, "triggerReportEnv", TriggerReportEnv);
NODE_SET_METHOD(exports, "triggerReportNoEnv", TriggerReportNoEnv); 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) NODE_MODULE(NODE_GYP_MODULE_NAME, init)

View File

@ -9,7 +9,7 @@ const tmpdir = require('../../common/tmpdir');
const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`); const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);
const addon = require(binding); const addon = require(binding);
function myAddonMain(method, hasJavaScriptFrames) { function myAddonMain(method, { hasIsolate, hasEnv }) {
tmpdir.refresh(); tmpdir.refresh();
process.report.directory = tmpdir.path; process.report.directory = tmpdir.path;
@ -19,26 +19,35 @@ function myAddonMain(method, hasJavaScriptFrames) {
assert.strictEqual(reports.length, 1); assert.strictEqual(reports.length, 1);
const report = reports[0]; const report = reports[0];
helper.validate(report); helper.validate(report, [
['header.event', 'FooMessage'],
['header.trigger', 'BarTrigger'],
]);
const content = require(report); const content = require(report);
assert.strictEqual(content.header.event, 'FooMessage');
assert.strictEqual(content.header.trigger, 'BarTrigger');
// Check that the javascript stack is present. // Check that the javascript stack is present.
if (hasJavaScriptFrames) { if (hasIsolate) {
assert.strictEqual(content.javascriptStack.stack.findIndex((frame) => frame.match('myAddonMain')), 0); assert.strictEqual(content.javascriptStack.stack.findIndex((frame) => frame.match('myAddonMain')), 0);
} else { } else {
assert.strictEqual(content.javascriptStack, undefined); assert.strictEqual(content.javascriptStack, undefined);
} }
if (hasEnv) {
assert.strictEqual(content.header.threadId, 0);
} else {
assert.strictEqual(content.header.threadId, null);
}
} }
const methods = [ const methods = [
['triggerReport', true], ['triggerReport', true, true],
['triggerReportNoIsolate', false], ['triggerReportNoIsolate', false, false],
['triggerReportEnv', true], ['triggerReportEnv', true, true],
['triggerReportNoEnv', false], ['triggerReportNoEnv', false, false],
['triggerReportNoContext', true, false],
['triggerReportNewContext', true, false],
]; ];
for (const [method, hasJavaScriptFrames] of methods) { for (const [method, hasIsolate, hasEnv] of methods) {
myAddonMain(method, hasJavaScriptFrames); myAddonMain(method, { hasIsolate, hasEnv });
} }