Skip to content

Commit b5ad655

Browse files
legendecastargos
authored andcommitted
lib: properly process JavaScript exceptions on async_hooks fatal error
JavaScript exceptions could be arbitrary values. PR-URL: #38106 Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 079d90b commit b5ad655

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

lib/internal/async_hooks.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ const { kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
9999
const { async_id_symbol,
100100
trigger_async_id_symbol } = internalBinding('symbols');
101101

102+
// Lazy load of internal/util/inspect;
103+
let inspect;
104+
102105
// Used in AsyncHook and AsyncResource.
103106
const init_symbol = Symbol('init');
104107
const before_symbol = Symbol('before');
@@ -155,12 +158,17 @@ function executionAsyncResource() {
155158
return lookupPublicResource(resource);
156159
}
157160

161+
function inspectExceptionValue(e) {
162+
inspect = inspect ?? require('internal/util/inspect').inspect;
163+
return { message: inspect(e) };
164+
}
165+
158166
// Used to fatally abort the process if a callback throws.
159167
function fatalError(e) {
160-
if (typeof e.stack === 'string') {
168+
if (typeof e?.stack === 'string') {
161169
process._rawDebug(e.stack);
162170
} else {
163-
const o = { message: e };
171+
const o = inspectExceptionValue(e);
164172
ErrorCaptureStackTrace(o, fatalError);
165173
process._rawDebug(o.stack);
166174
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const childProcess = require('child_process');
5+
const os = require('os');
6+
7+
if (process.argv[2] === 'child') {
8+
child(process.argv[3], process.argv[4]);
9+
} else {
10+
main();
11+
}
12+
13+
function child(type, valueType) {
14+
const { createHook } = require('async_hooks');
15+
const fs = require('fs');
16+
17+
createHook({
18+
[type]() {
19+
if (valueType === 'symbol') {
20+
throw Symbol('foo');
21+
}
22+
// eslint-disable-next-line no-throw-literal
23+
throw null;
24+
}
25+
}).enable();
26+
27+
// Trigger `promiseResolve`.
28+
new Promise((resolve) => resolve())
29+
// Trigger `after`/`destroy`.
30+
.then(() => fs.promises.readFile(__filename, 'utf8'))
31+
// Make process exit with code 0 if no error caught.
32+
.then(() => process.exit(0));
33+
}
34+
35+
function main() {
36+
const types = [ 'init', 'before', 'after', 'destroy', 'promiseResolve' ];
37+
const valueTypes = [
38+
[ 'null', 'Error: null' ],
39+
[ 'symbol', 'Error: Symbol(foo)' ],
40+
];
41+
for (const type of types) {
42+
for (const [valueType, expect] of valueTypes) {
43+
const cp = childProcess.spawnSync(
44+
process.execPath,
45+
[ __filename, 'child', type, valueType ],
46+
{
47+
encoding: 'utf8',
48+
});
49+
assert.strictEqual(cp.status, 1, type);
50+
assert.strictEqual(cp.stderr.trim().split(os.EOL)[0], expect, type);
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)