Skip to content

Commit

Permalink
report: skip report if uncaught exception is handled
Browse files Browse the repository at this point in the history
If the exception is handled by the userland
process#uncaughtException handler, reports should not be generated
repetitively as the process may continue to run.
  • Loading branch information
legendecas committed Aug 11, 2022
1 parent ec44403 commit 360e9f7
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 57 deletions.
21 changes: 0 additions & 21 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,27 +151,6 @@ function createOnGlobalUncaughtException() {
// call that threw and was never cleared. So clear it now.
clearDefaultTriggerAsyncId();

// If diagnostic reporting is enabled, call into its handler to see
// whether it is interested in handling the situation.
// Ignore if the error is scoped inside a domain.
// use == in the checks as we want to allow for null and undefined
if (er == null || er.domain == null) {
try {
const report = internalBinding('report');
if (report != null && report.shouldReportOnUncaughtException()) {
report.writeReport(
typeof er?.message === 'string' ?
er.message :
'Exception',
'Exception',
null,
er ?? {});
}
} catch {
// Ignore the exception. Diagnostic reporting is unavailable.
}
}

const type = fromPromise ? 'unhandledRejection' : 'uncaughtException';
process.emit('uncaughtExceptionMonitor', er, type);
if (exceptionHandlerState.captureFn !== null) {
Expand Down
8 changes: 8 additions & 0 deletions src/node_errors.cc
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ static void ReportFatalException(Environment* env,
}

node::Utf8Value trace(env->isolate(), stack_trace);
std::string report_message = "Exception";

// range errors have a trace member set to undefined
if (trace.length() > 0 && !stack_trace->IsUndefined()) {
Expand Down Expand Up @@ -424,6 +425,8 @@ static void ReportFatalException(Environment* env,
} else {
node::Utf8Value name_string(env->isolate(), name.ToLocalChecked());
node::Utf8Value message_string(env->isolate(), message.ToLocalChecked());
// Update the report message if it is an object has message property.
report_message = message_string.ToString();

if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
FPrintF(stderr, "%s: %s\n", name_string, message_string);
Expand All @@ -445,6 +448,11 @@ static void ReportFatalException(Environment* env,
}
}

if (env->isolate_data()->options()->report_uncaught_exception) {
report::TriggerNodeReport(
isolate, env, report_message.c_str(), "Exception", "", error);
}

if (env->options()->trace_uncaught) {
Local<StackTrace> trace = message->GetStackTrace();
if (!trace.IsEmpty()) {
Expand Down
36 changes: 32 additions & 4 deletions test/report/test-report-uncaught-exception-compat.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
// Flags: --experimental-report --report-uncaught-exception --report-compact
'use strict';
// Test producing a compact report on uncaught exception.
require('../common');
require('./test-report-uncaught-exception.js');
// Test producing a report on uncaught exception.
const common = require('../common');
const assert = require('assert');
const childProcess = require('child_process');
const helper = require('../common/report');
const tmpdir = require('../common/tmpdir');

if (process.argv[2] === 'child') {
process.report.directory = process.argv[3];

throw new Error('test error');
}

tmpdir.refresh();
const child = childProcess.spawn(process.execPath, [
'--report-uncaught-exception',
'--report-compact',
__filename,
'child',
tmpdir.path,
]);
child.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 1);
const reports = helper.findReports(child.pid, tmpdir.path);
assert.strictEqual(reports.length, 1);

helper.validate(reports[0], [
['header.event', 'Exception'],
['header.trigger', 'Exception'],
['javascriptStack.message', 'Error: test error'],
]);
}));
23 changes: 23 additions & 0 deletions test/report/test-report-uncaught-exception-handled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Flags: --report-uncaught-exception
'use strict';
// Test report is suppressed on uncaught exception hook.
const common = require('../common');
const assert = require('assert');
const helper = require('../common/report');
const tmpdir = require('../common/tmpdir');
const error = new Error('test error');

tmpdir.refresh();
process.report.directory = tmpdir.path;

// Make sure the uncaughtException listener is called.
process.on('uncaughtException', common.mustCall());

process.on('exit', (code) => {
assert.strictEqual(code, 0);
// Make sure no reports are generated.
const reports = helper.findReports(process.pid, tmpdir.path);
assert.strictEqual(reports.length, 0);
});

throw error;
4 changes: 1 addition & 3 deletions test/report/test-report-uncaught-exception-override.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ process.report.directory = tmpdir.path;

// First, install an uncaught exception hook.
process.setUncaughtExceptionCaptureCallback(common.mustCall());

// Make sure this is ignored due to the above override.
process.on('uncaughtException', common.mustNotCall());
// Do not install process uncaughtException handler.

process.on('exit', (code) => {
assert.strictEqual(code, 0);
Expand Down
28 changes: 18 additions & 10 deletions test/report/test-report-uncaught-exception-primitives.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
// Flags: --report-uncaught-exception
'use strict';
// Test producing a report on uncaught exception.
const common = require('../common');
const assert = require('assert');
const childProcess = require('child_process');
const helper = require('../common/report');
const tmpdir = require('../common/tmpdir');

const exception = 1;
if (process.argv[2] === 'child') {
process.report.directory = process.argv[3];

tmpdir.refresh();
process.report.directory = tmpdir.path;
// eslint-disable-next-line no-throw-literal
throw 1;
}

process.on('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err, exception);
const reports = helper.findReports(process.pid, tmpdir.path);
tmpdir.refresh();
const child = childProcess.spawn(process.execPath, [
'--report-uncaught-exception',
__filename,
'child',
tmpdir.path,
]);
child.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 1);
const reports = helper.findReports(child.pid, tmpdir.path);
assert.strictEqual(reports.length, 1);

helper.validate(reports[0], [
['header.event', 'Exception'],
['javascriptStack.message', `${exception}`],
['header.trigger', 'Exception'],
['javascriptStack.message', '1'],
]);
}));

throw exception;
25 changes: 16 additions & 9 deletions test/report/test-report-uncaught-exception-symbols.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
// Flags: --report-uncaught-exception
'use strict';
// Test producing a report on uncaught exception.
const common = require('../common');
const assert = require('assert');
const childProcess = require('child_process');
const helper = require('../common/report');
const tmpdir = require('../common/tmpdir');

const exception = Symbol('foobar');
if (process.argv[2] === 'child') {
process.report.directory = process.argv[3];

tmpdir.refresh();
process.report.directory = tmpdir.path;
throw Symbol('foobar');
}

process.on('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err, exception);
const reports = helper.findReports(process.pid, tmpdir.path);
tmpdir.refresh();
const child = childProcess.spawn(process.execPath, [
'--report-uncaught-exception',
__filename,
'child',
tmpdir.path,
]);
child.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 1);
const reports = helper.findReports(child.pid, tmpdir.path);
assert.strictEqual(reports.length, 1);

helper.validate(reports[0], [
['header.event', 'Exception'],
['header.trigger', 'Exception'],
['javascriptStack.message', 'Symbol(foobar)'],
]);
}));

throw exception;
32 changes: 22 additions & 10 deletions test/report/test-report-uncaught-exception.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
// Flags: --report-uncaught-exception
'use strict';
// Test producing a report on uncaught exception.
const common = require('../common');
const assert = require('assert');
const childProcess = require('child_process');
const helper = require('../common/report');
const tmpdir = require('../common/tmpdir');
const error = new Error('test error');

tmpdir.refresh();
process.report.directory = tmpdir.path;
if (process.argv[2] === 'child') {
process.report.directory = process.argv[3];

throw new Error('test error');
}

process.on('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err, error);
const reports = helper.findReports(process.pid, tmpdir.path);
tmpdir.refresh();
const child = childProcess.spawn(process.execPath, [
'--report-uncaught-exception',
__filename,
'child',
tmpdir.path,
]);
child.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 1);
const reports = helper.findReports(child.pid, tmpdir.path);
assert.strictEqual(reports.length, 1);
helper.validate(reports[0]);
}));

throw error;
helper.validate(reports[0], [
['header.event', 'Exception'],
['header.trigger', 'Exception'],
['javascriptStack.message', 'Error: test error'],
]);
}));

0 comments on commit 360e9f7

Please sign in to comment.