Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion src/node_errors.cc
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,10 @@ void OnFatalError(const char* location, const char* message) {
}

Isolate* isolate = Isolate::GetCurrent();
Environment* env = Environment::GetCurrent(isolate);
Environment* env = nullptr;
if (isolate != nullptr) {
env = Environment::GetCurrent(isolate);
}
bool report_on_fatalerror;
{
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
Expand Down
20 changes: 11 additions & 9 deletions src/node_report.cc
Original file line number Diff line number Diff line change
Expand Up @@ -267,20 +267,22 @@ static void WriteNodeReport(Isolate* isolate,
PrintVersionInformation(&writer);
writer.json_objectend();

writer.json_objectstart("javascriptStack");
// Report summary JavaScript error stack backtrace
PrintJavaScriptErrorStack(&writer, isolate, error, trigger);
if (isolate != nullptr) {
writer.json_objectstart("javascriptStack");
// Report summary JavaScript error stack backtrace
PrintJavaScriptErrorStack(&writer, isolate, error, trigger);

// Report summary JavaScript error properties backtrace
PrintJavaScriptErrorProperties(&writer, isolate, error);
writer.json_objectend(); // the end of 'javascriptStack'
// Report summary JavaScript error properties backtrace
PrintJavaScriptErrorProperties(&writer, isolate, error);
writer.json_objectend(); // the end of 'javascriptStack'

// Report V8 Heap and Garbage Collector information
PrintGCStatistics(&writer, isolate);
}

// Report native stack backtrace
PrintNativeStack(&writer);

// Report V8 Heap and Garbage Collector information
PrintGCStatistics(&writer, isolate);

// Report OS and current thread resource usage
PrintResourceUsage(&writer);

Expand Down
73 changes: 39 additions & 34 deletions test/common/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,20 @@ function validateContent(report, fields = []) {

function _validateContent(report, fields = []) {
const isWindows = process.platform === 'win32';
const isJavaScriptThreadReport = report.javascriptStack != null;

// Verify that all sections are present as own properties of the report.
const sections = ['header', 'javascriptStack', 'nativeStack',
'javascriptHeap', 'libuv', 'environmentVariables',
const sections = ['header', 'nativeStack', 'libuv', 'environmentVariables',
'sharedObjects', 'resourceUsage', 'workers'];
if (!isWindows)
sections.push('userLimits');

if (report.uvthreadResourceUsage)
sections.push('uvthreadResourceUsage');

if (isJavaScriptThreadReport)
sections.push('javascriptStack', 'javascriptHeap');

checkForUnknownFields(report, sections);
sections.forEach((section) => {
assert(report.hasOwnProperty(section));
Expand Down Expand Up @@ -163,19 +166,6 @@ function _validateContent(report, fields = []) {
});
assert.strictEqual(header.host, os.hostname());

// Verify the format of the javascriptStack section.
checkForUnknownFields(report.javascriptStack,
['message', 'stack', 'errorProperties']);
assert.strictEqual(typeof report.javascriptStack.errorProperties,
'object');
assert.strictEqual(typeof report.javascriptStack.message, 'string');
if (report.javascriptStack.stack !== undefined) {
assert(Array.isArray(report.javascriptStack.stack));
report.javascriptStack.stack.forEach((frame) => {
assert.strictEqual(typeof frame, 'string');
});
}

// Verify the format of the nativeStack section.
assert(Array.isArray(report.nativeStack));
report.nativeStack.forEach((frame) => {
Expand All @@ -186,26 +176,41 @@ function _validateContent(report, fields = []) {
assert.strictEqual(typeof frame.symbol, 'string');
});

// Verify the format of the javascriptHeap section.
const heap = report.javascriptHeap;
const jsHeapFields = ['totalMemory', 'totalCommittedMemory', 'usedMemory',
'availableMemory', 'memoryLimit', 'heapSpaces'];
checkForUnknownFields(heap, jsHeapFields);
assert(Number.isSafeInteger(heap.totalMemory));
assert(Number.isSafeInteger(heap.totalCommittedMemory));
assert(Number.isSafeInteger(heap.usedMemory));
assert(Number.isSafeInteger(heap.availableMemory));
assert(Number.isSafeInteger(heap.memoryLimit));
assert(typeof heap.heapSpaces === 'object' && heap.heapSpaces !== null);
const heapSpaceFields = ['memorySize', 'committedMemory', 'capacity', 'used',
'available'];
Object.keys(heap.heapSpaces).forEach((spaceName) => {
const space = heap.heapSpaces[spaceName];
checkForUnknownFields(space, heapSpaceFields);
heapSpaceFields.forEach((field) => {
assert(Number.isSafeInteger(space[field]));
if (isJavaScriptThreadReport) {
// Verify the format of the javascriptStack section.
checkForUnknownFields(report.javascriptStack,
['message', 'stack', 'errorProperties']);
assert.strictEqual(typeof report.javascriptStack.errorProperties,
'object');
assert.strictEqual(typeof report.javascriptStack.message, 'string');
if (report.javascriptStack.stack !== undefined) {
assert(Array.isArray(report.javascriptStack.stack));
report.javascriptStack.stack.forEach((frame) => {
assert.strictEqual(typeof frame, 'string');
});
}

// Verify the format of the javascriptHeap section.
const heap = report.javascriptHeap;
const jsHeapFields = ['totalMemory', 'totalCommittedMemory', 'usedMemory',
'availableMemory', 'memoryLimit', 'heapSpaces'];
checkForUnknownFields(heap, jsHeapFields);
assert(Number.isSafeInteger(heap.totalMemory));
assert(Number.isSafeInteger(heap.totalCommittedMemory));
assert(Number.isSafeInteger(heap.usedMemory));
assert(Number.isSafeInteger(heap.availableMemory));
assert(Number.isSafeInteger(heap.memoryLimit));
assert(typeof heap.heapSpaces === 'object' && heap.heapSpaces !== null);
const heapSpaceFields = ['memorySize', 'committedMemory', 'capacity',
'used', 'available'];
Object.keys(heap.heapSpaces).forEach((spaceName) => {
const space = heap.heapSpaces[spaceName];
checkForUnknownFields(space, heapSpaceFields);
heapSpaceFields.forEach((field) => {
assert(Number.isSafeInteger(space[field]));
});
});
});
}

// Verify the format of the resourceUsage section.
const usage = report.resourceUsage;
Expand Down
1 change: 1 addition & 0 deletions test/node-api/test_fatal/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ const p = child_process.spawnSync(
assert.ifError(p.error);
assert.ok(p.stderr.toString().includes(
'FATAL ERROR: test_fatal::Test fatal message'));
assert.ok(p.status === 134 || p.signal === 'SIGABRT');
1 change: 1 addition & 0 deletions test/node-api/test_fatal/test2.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ const p = child_process.spawnSync(
assert.ifError(p.error);
assert.ok(p.stderr.toString().includes(
'FATAL ERROR: test_fatal::Test fatal message'));
assert.ok(p.status === 134 || p.signal === 'SIGABRT');
21 changes: 21 additions & 0 deletions test/node-api/test_fatal/test_fatal.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
// For the purpose of this test we use libuv's threading library. When deciding
// on a threading library for a new project it bears remembering that in the
// future libuv may introduce API changes which may render it non-ABI-stable,
// which, in turn, may affect the ABI stability of the project despite its use
// of N-API.
#include <uv.h>
#include <node_api.h>
#include "../../js-native-api/common.h"

static uv_thread_t uv_thread;

static void work_thread(void* data) {
napi_fatal_error("work_thread", NAPI_AUTO_LENGTH,
"foobar", NAPI_AUTO_LENGTH);
}

static napi_value Test(napi_env env, napi_callback_info info) {
napi_fatal_error("test_fatal::Test", NAPI_AUTO_LENGTH,
"fatal message", NAPI_AUTO_LENGTH);
return NULL;
}

static napi_value TestThread(napi_env env, napi_callback_info info) {
NAPI_ASSERT(env,
(uv_thread_create(&uv_thread, work_thread, NULL) == 0),
"Thread creation");
return NULL;
}

static napi_value TestStringLength(napi_env env, napi_callback_info info) {
napi_fatal_error("test_fatal::TestStringLength", 16, "fatal message", 13);
return NULL;
Expand All @@ -16,6 +36,7 @@ static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor properties[] = {
DECLARE_NAPI_PROPERTY("Test", Test),
DECLARE_NAPI_PROPERTY("TestStringLength", TestStringLength),
DECLARE_NAPI_PROPERTY("TestThread", TestThread),
};

NAPI_CALL(env, napi_define_properties(
Expand Down
20 changes: 20 additions & 0 deletions test/node-api/test_fatal/test_threads.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const child_process = require('child_process');
const test_fatal = require(`./build/${common.buildType}/test_fatal`);

// Test in a child process because the test code will trigger a fatal error
// that crashes the process.
if (process.argv[2] === 'child') {
test_fatal.TestThread();
// Busy loop to allow the work thread to abort.
while (true) {}
}

const p = child_process.spawnSync(
process.execPath, [ __filename, 'child' ]);
assert.ifError(p.error);
assert.ok(p.stderr.toString().includes(
'FATAL ERROR: work_thread foobar'));
assert.ok(p.status === 134 || p.signal === 'SIGABRT');
36 changes: 36 additions & 0 deletions test/node-api/test_fatal/test_threads_report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';
const common = require('../../common');
const helper = require('../../common/report.js');
const tmpdir = require('../../common/tmpdir');

const assert = require('assert');
const child_process = require('child_process');
const test_fatal = require(`./build/${common.buildType}/test_fatal`);

if (common.buildType === 'Debug')
common.skip('as this will currently fail with a Debug check ' +
'in v8::Isolate::GetCurrent()');

// Test in a child process because the test code will trigger a fatal error
// that crashes the process.
if (process.argv[2] === 'child') {
test_fatal.TestThread();
// Busy loop to allow the work thread to abort.
while (true) {}
}

tmpdir.refresh();
const p = child_process.spawnSync(
process.execPath,
[ '--report-on-fatalerror', __filename, 'child' ],
{ cwd: tmpdir.path });
assert.ifError(p.error);
assert.ok(p.stderr.toString().includes(
'FATAL ERROR: work_thread foobar'));
assert.ok(p.status === 134 || p.signal === 'SIGABRT');

const reports = helper.findReports(p.pid, tmpdir.path);
assert.strictEqual(reports.length, 1);

const report = reports[0];
helper.validate(report);