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: https://github.com/nodejs/node/pull/29513
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Anna Henningsen 2019-09-09 23:43:21 +02:00 committed by Daniel Bevenius
parent 233cdb64a9
commit e095e645e5
3 changed files with 46 additions and 1 deletions

View File

@ -10,6 +10,7 @@
namespace node {
using errors::TryCatchScope;
using v8::Array;
using v8::Context;
using v8::Function;
@ -111,8 +112,17 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
}
Local<Value> args[] = { type, promise, value };
// V8 does not expect this callback to have a scheduled exceptions once it
// returns, so we print them out in a best effort to do something about it
// without failing silently and without crashing the process.
TryCatchScope try_catch(env);
USE(callback->Call(
env->context(), Undefined(isolate), arraysize(args), args));
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
fprintf(stderr, "Exception in PromiseRejectCallback:\n");
PrintCaughtException(isolate, env->context(), try_catch);
}
}
static void SetPromiseRejectCallback(

View File

@ -23,4 +23,4 @@ assert.strictEqual(ret.status, 0,
`EXIT CODE: ${ret.status}, STDERR:\n${ret.stderr}`);
const stderr = ret.stderr.toString('utf8', 0, 2048);
assert.ok(!/async.*hook/i.test(stderr));
assert.ok(stderr.includes('UnhandledPromiseRejectionWarning: Error'), stderr);
assert.ok(stderr.includes('Maximum call stack size exceeded'), stderr);

View File

@ -0,0 +1,35 @@
'use strict';
require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const path = require('path');
const child_process = require('child_process');
tmpdir.refresh();
// Tests that exceptions from the PromiseRejectCallback are printed to stderr
// when they occur as a best-effort way of handling them, and that calling
// `console.log()` works after that. Earlier, the latter did not work because
// of the exception left lying around by the PromiseRejectCallback when its JS
// part exceeded the call stack limit, and when the inspector/built-in coverage
// was enabled, it resulted in a hard crash.
for (const NODE_V8_COVERAGE of ['', tmpdir.path]) {
// NODE_V8_COVERAGE does not work without the inspector.
// Refs: https://github.com/nodejs/node/issues/29542
if (!process.features.inspector && NODE_V8_COVERAGE !== '') continue;
const { status, signal, stdout, stderr } =
child_process.spawnSync(process.execPath,
[path.join(__dirname, 'test-ttywrap-stack.js')],
{ env: { ...process.env, NODE_V8_COVERAGE } });
assert(stdout.toString('utf8')
.startsWith('RangeError: Maximum call stack size exceeded'),
`stdout: <${stdout}>`);
assert(stderr.toString('utf8')
.startsWith('Exception in PromiseRejectCallback'),
`stderr: <${stderr}>`);
assert.strictEqual(status, 0);
assert.strictEqual(signal, null);
}