report: skip report if uncaught exception is handled

If the exception is handled by the userland
process#uncaughtException handler, reports should not be generated
repetitively as the process may continue to run.

PR-URL: https://github.com/nodejs/node/pull/44208
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
This commit is contained in:
Chengzhong Wu 2022-08-16 21:04:48 +08:00 committed by legendecas
parent 5e5fb825fc
commit 678551c294
No known key found for this signature in database
GPG key ID: CB3C9EC2BC27057C
9 changed files with 122 additions and 60 deletions

View file

@ -1139,6 +1139,9 @@ Default signal is `SIGUSR2`.
<!-- YAML
added: v11.8.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44208
description: Report is not generated if the uncaught exception is handled.
- version:
- v13.12.0
- v12.17.0
@ -1150,9 +1153,9 @@ changes:
`--report-uncaught-exception`.
-->
Enables report to be generated on uncaught exceptions. Useful when inspecting
the JavaScript stack in conjunction with native stack and other runtime
environment data.
Enables report to be generated when the process exits due to an uncaught
exception. Useful when inspecting the JavaScript stack in conjunction with
native stack and other runtime environment data.
### `--secure-heap=n`

View file

@ -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) {

View file

@ -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()) {
@ -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);
@ -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()) {

View file

@ -1,5 +1,32 @@
// 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') {
throw new Error('test error');
}
tmpdir.refresh();
const child = childProcess.spawn(process.execPath, [
'--report-uncaught-exception',
'--report-compact',
__filename,
'child',
], {
cwd: 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'],
]);
}));

View file

@ -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;

View file

@ -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);

View file

@ -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 = 1;
if (process.argv[2] === 'child') {
// eslint-disable-next-line no-throw-literal
throw 1;
}
tmpdir.refresh();
process.report.directory = tmpdir.path;
process.on('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err, exception);
const reports = helper.findReports(process.pid, tmpdir.path);
const child = childProcess.spawn(process.execPath, [
'--report-uncaught-exception',
__filename,
'child',
], {
cwd: 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;

View file

@ -1,25 +1,31 @@
// 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') {
throw Symbol('foobar');
}
tmpdir.refresh();
process.report.directory = tmpdir.path;
process.on('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err, exception);
const reports = helper.findReports(process.pid, tmpdir.path);
const child = childProcess.spawn(process.execPath, [
'--report-uncaught-exception',
__filename,
'child',
], {
cwd: 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;

View file

@ -1,20 +1,31 @@
// 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');
if (process.argv[2] === 'child') {
throw new Error('test error');
}
tmpdir.refresh();
process.report.directory = tmpdir.path;
process.on('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err, error);
const reports = helper.findReports(process.pid, tmpdir.path);
const child = childProcess.spawn(process.execPath, [
'--report-uncaught-exception',
__filename,
'child',
], {
cwd: 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'],
]);
}));