Skip to content

Commit 51a1dfa

Browse files
addaleaxBridgeAR
authored andcommitted
src: print exceptions from PromiseRejectCallback
Previously, leaving the exception lying around would leave the JS engine in an invalid state, as it was not expecting exceptions to be thrown from the C++ `PromiseRejectCallback`, and lead to hard crashes under some conditions (e.g. with coverage enabled). PR-URL: #29513 Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent f90740d commit 51a1dfa

3 files changed

+46
-1
lines changed

src/node_task_queue.cc

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
namespace node {
1212

13+
using errors::TryCatchScope;
1314
using v8::Array;
1415
using v8::Context;
1516
using v8::Function;
@@ -111,8 +112,17 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
111112
}
112113

113114
Local<Value> args[] = { type, promise, value };
115+
116+
// V8 does not expect this callback to have a scheduled exceptions once it
117+
// returns, so we print them out in a best effort to do something about it
118+
// without failing silently and without crashing the process.
119+
TryCatchScope try_catch(env);
114120
USE(callback->Call(
115121
env->context(), Undefined(isolate), arraysize(args), args));
122+
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
123+
fprintf(stderr, "Exception in PromiseRejectCallback:\n");
124+
PrintCaughtException(isolate, env->context(), try_catch);
125+
}
116126
}
117127

118128
static void SetPromiseRejectCallback(

test/parallel/test-async-wrap-pop-id-during-load.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ assert.strictEqual(ret.status, 0,
2323
`EXIT CODE: ${ret.status}, STDERR:\n${ret.stderr}`);
2424
const stderr = ret.stderr.toString('utf8', 0, 2048);
2525
assert.ok(!/async.*hook/i.test(stderr));
26-
assert.ok(stderr.includes('UnhandledPromiseRejectionWarning: Error'), stderr);
26+
assert.ok(stderr.includes('Maximum call stack size exceeded'), stderr);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
require('../common');
3+
const tmpdir = require('../common/tmpdir');
4+
const assert = require('assert');
5+
const path = require('path');
6+
const child_process = require('child_process');
7+
8+
tmpdir.refresh();
9+
10+
// Tests that exceptions from the PromiseRejectCallback are printed to stderr
11+
// when they occur as a best-effort way of handling them, and that calling
12+
// `console.log()` works after that. Earlier, the latter did not work because
13+
// of the exception left lying around by the PromiseRejectCallback when its JS
14+
// part exceeded the call stack limit, and when the inspector/built-in coverage
15+
// was enabled, it resulted in a hard crash.
16+
17+
for (const NODE_V8_COVERAGE of ['', tmpdir.path]) {
18+
// NODE_V8_COVERAGE does not work without the inspector.
19+
// Refs: https://github.com/nodejs/node/issues/29542
20+
if (!process.features.inspector && NODE_V8_COVERAGE !== '') continue;
21+
22+
const { status, signal, stdout, stderr } =
23+
child_process.spawnSync(process.execPath,
24+
[path.join(__dirname, 'test-ttywrap-stack.js')],
25+
{ env: { ...process.env, NODE_V8_COVERAGE } });
26+
27+
assert(stdout.toString('utf8')
28+
.startsWith('RangeError: Maximum call stack size exceeded'),
29+
`stdout: <${stdout}>`);
30+
assert(stderr.toString('utf8')
31+
.startsWith('Exception in PromiseRejectCallback'),
32+
`stderr: <${stderr}>`);
33+
assert.strictEqual(status, 0);
34+
assert.strictEqual(signal, null);
35+
}

0 commit comments

Comments
 (0)