Skip to content

Commit 4f5bead

Browse files
committed
lib: properly processing JavaScript exceptions on async_hooks fatal error
JavaScript exceptions could be arbitrary values.
1 parent a9332e8 commit 4f5bead

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

lib/internal/async_hooks.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ const {
100100
const { async_id_symbol,
101101
trigger_async_id_symbol } = internalBinding('symbols');
102102

103+
// Lazy load of internal/util/inspect;
104+
let inspect;
105+
103106
// Used in AsyncHook and AsyncResource.
104107
const init_symbol = Symbol('init');
105108
const before_symbol = Symbol('before');
@@ -156,12 +159,20 @@ function executionAsyncResource() {
156159
return lookupPublicResource(resource);
157160
}
158161

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

0 commit comments

Comments
 (0)