Skip to content

Commit ae85d5f

Browse files
TimothyGuMylesBorins
authored andcommitted
promises: more robust stringification
PR-URL: #13784 Fixes: #13771 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Rod Vagg <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 2310cfc commit ae85d5f

File tree

3 files changed

+56
-4
lines changed

3 files changed

+56
-4
lines changed

lib/internal/process/promises.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const { safeToString } = process.binding('util');
4+
35
const promiseRejectEvent = process._promiseRejectEvent;
46
const hasBeenNotifiedProperty = new WeakMap();
57
const promiseToGuidProperty = new WeakMap();
@@ -58,12 +60,17 @@ function setupPromises(scheduleMicrotasks) {
5860
}
5961

6062
function emitWarning(uid, reason) {
61-
const warning = new Error('Unhandled promise rejection ' +
62-
`(rejection id: ${uid}): ${String(reason)}`);
63+
const warning = new Error(
64+
`Unhandled promise rejection (rejection id: ${uid}): ` +
65+
safeToString(reason));
6366
warning.name = 'UnhandledPromiseRejectionWarning';
6467
warning.id = uid;
65-
if (reason instanceof Error) {
66-
warning.stack = reason.stack;
68+
try {
69+
if (reason instanceof Error) {
70+
warning.stack = reason.stack;
71+
}
72+
} catch (err) {
73+
// ignored
6774
}
6875
process.emitWarning(warning);
6976
if (!deprecationWarned) {

src/node_util.cc

+7
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ static void GetProxyDetails(const FunctionCallbackInfo<Value>& args) {
8484
args.GetReturnValue().Set(ret);
8585
}
8686

87+
// Side effect-free stringification that will never throw exceptions.
88+
static void SafeToString(const FunctionCallbackInfo<Value>& args) {
89+
auto context = args.GetIsolate()->GetCurrentContext();
90+
args.GetReturnValue().Set(args[0]->ToDetailString(context).ToLocalChecked());
91+
}
92+
8793
inline Local<Private> IndexToPrivateSymbol(Environment* env, uint32_t index) {
8894
#define V(name, _) &Environment::name,
8995
static Local<Private> (Environment::*const methods[])() const = {
@@ -221,6 +227,7 @@ void Initialize(Local<Object> target,
221227
env->SetMethod(target, "setHiddenValue", SetHiddenValue);
222228
env->SetMethod(target, "getPromiseDetails", GetPromiseDetails);
223229
env->SetMethod(target, "getProxyDetails", GetProxyDetails);
230+
env->SetMethod(target, "safeToString", SafeToString);
224231

225232
env->SetMethod(target, "startSigintWatchdog", StartSigintWatchdog);
226233
env->SetMethod(target, "stopSigintWatchdog", StopSigintWatchdog);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
const expectedDeprecationWarning = 'Unhandled promise rejections are ' +
5+
'deprecated. In the future, promise ' +
6+
'rejections that are not handled will ' +
7+
'terminate the Node.js process with a ' +
8+
'non-zero exit code.';
9+
const expectedPromiseWarning = 'Unhandled promise rejection (rejection id: ' +
10+
'1): [object Object]';
11+
12+
function throwErr() {
13+
throw new Error('Error from proxy');
14+
}
15+
16+
const thorny = new Proxy({}, {
17+
getPrototypeOf: throwErr,
18+
setPrototypeOf: throwErr,
19+
isExtensible: throwErr,
20+
preventExtensions: throwErr,
21+
getOwnPropertyDescriptor: throwErr,
22+
defineProperty: throwErr,
23+
has: throwErr,
24+
get: throwErr,
25+
set: throwErr,
26+
deleteProperty: throwErr,
27+
ownKeys: throwErr,
28+
apply: throwErr,
29+
construct: throwErr
30+
});
31+
32+
common.expectWarning({
33+
DeprecationWarning: expectedDeprecationWarning,
34+
UnhandledPromiseRejectionWarning: expectedPromiseWarning,
35+
});
36+
37+
// ensure this doesn't crash
38+
Promise.reject(thorny);

0 commit comments

Comments
 (0)