Skip to content

Commit 3a4edc8

Browse files
committed
util: use more defensive code when inspecting error objects
PR-URL: #60139 Fixes: #60107 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent c86a69f commit 3a4edc8

File tree

3 files changed

+89
-24
lines changed

3 files changed

+89
-24
lines changed

lib/internal/util.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
Error,
99
ErrorCaptureStackTrace,
1010
FunctionPrototypeCall,
11+
FunctionPrototypeSymbolHasInstance,
1112
NumberParseInt,
1213
ObjectDefineProperties,
1314
ObjectDefineProperty,
@@ -96,7 +97,7 @@ function isError(e) {
9697
// An error could be an instance of Error while not being a native error
9798
// or could be from a different realm and not be instance of Error but still
9899
// be a native error.
99-
return isNativeError(e) || e instanceof Error;
100+
return isNativeError(e) || FunctionPrototypeSymbolHasInstance(Error, e);
100101
}
101102

102103
// Keep a list of deprecation codes that have been warned on so we only warn on

lib/internal/util/inspect.js

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const {
7272
ObjectPrototype,
7373
ObjectPrototypeHasOwnProperty,
7474
ObjectPrototypePropertyIsEnumerable,
75+
ObjectPrototypeToString,
7576
ObjectSeal,
7677
ObjectSetPrototypeOf,
7778
Promise,
@@ -1461,13 +1462,19 @@ function getDuplicateErrorFrameRanges(frames) {
14611462
}
14621463

14631464
function getStackString(ctx, error) {
1464-
if (error.stack) {
1465-
if (typeof error.stack === 'string') {
1466-
return error.stack;
1465+
let stack;
1466+
try {
1467+
stack = error.stack;
1468+
} catch {
1469+
// If stack is getter that throws, we ignore the error.
1470+
}
1471+
if (stack) {
1472+
if (typeof stack === 'string') {
1473+
return stack;
14671474
}
14681475
ctx.seen.push(error);
14691476
ctx.indentationLvl += 4;
1470-
const result = formatValue(ctx, error.stack);
1477+
const result = formatValue(ctx, stack);
14711478
ctx.indentationLvl -= 4;
14721479
ctx.seen.pop();
14731480
return `${ErrorPrototypeToString(error)}\n ${result}`;
@@ -1563,18 +1570,6 @@ function improveStack(stack, constructor, name, tag) {
15631570
return stack;
15641571
}
15651572

1566-
function removeDuplicateErrorKeys(ctx, keys, err, stack) {
1567-
if (!ctx.showHidden && keys.length !== 0) {
1568-
for (const name of ['name', 'message', 'stack']) {
1569-
const index = ArrayPrototypeIndexOf(keys, name);
1570-
// Only hide the property if it's a string and if it's part of the original stack
1571-
if (index !== -1 && (typeof err[name] !== 'string' || StringPrototypeIncludes(stack, err[name]))) {
1572-
ArrayPrototypeSplice(keys, index, 1);
1573-
}
1574-
}
1575-
}
1576-
}
1577-
15781573
function markNodeModules(ctx, line) {
15791574
let tempLine = '';
15801575
let lastPos = 0;
@@ -1657,28 +1652,72 @@ function safeGetCWD() {
16571652
}
16581653

16591654
function formatError(err, constructor, tag, ctx, keys) {
1660-
const name = err.name != null ? err.name : 'Error';
1661-
let stack = getStackString(ctx, err);
1655+
let message, name, stack;
1656+
try {
1657+
stack = getStackString(ctx, err);
1658+
} catch {
1659+
return ObjectPrototypeToString(err);
1660+
}
16621661

1663-
removeDuplicateErrorKeys(ctx, keys, err, stack);
1662+
let messageIsGetterThatThrows = false;
1663+
try {
1664+
message = err.message;
1665+
} catch {
1666+
messageIsGetterThatThrows = true;
1667+
}
1668+
let nameIsGetterThatThrows = false;
1669+
try {
1670+
name = err.name;
1671+
} catch {
1672+
nameIsGetterThatThrows = true;
1673+
}
1674+
1675+
if (!ctx.showHidden && keys.length !== 0) {
1676+
const index = ArrayPrototypeIndexOf(keys, 'stack');
1677+
if (index !== -1) {
1678+
ArrayPrototypeSplice(keys, index, 1);
1679+
}
1680+
1681+
if (!messageIsGetterThatThrows) {
1682+
const index = ArrayPrototypeIndexOf(keys, 'message');
1683+
// Only hide the property if it's a string and if it's part of the original stack
1684+
if (index !== -1 && (typeof message !== 'string' || StringPrototypeIncludes(stack, message))) {
1685+
ArrayPrototypeSplice(keys, index, 1);
1686+
}
1687+
}
1688+
1689+
if (!nameIsGetterThatThrows) {
1690+
const index = ArrayPrototypeIndexOf(keys, 'name');
1691+
// Only hide the property if it's a string and if it's part of the original stack
1692+
if (index !== -1 && (typeof name !== 'string' || StringPrototypeIncludes(stack, name))) {
1693+
ArrayPrototypeSplice(keys, index, 1);
1694+
}
1695+
}
1696+
}
1697+
name ??= 'Error';
16641698

16651699
if ('cause' in err &&
16661700
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
16671701
ArrayPrototypePush(keys, 'cause');
16681702
}
16691703

16701704
// Print errors aggregated into AggregateError
1671-
if (ArrayIsArray(err.errors) &&
1705+
try {
1706+
const errors = err.errors;
1707+
if (ArrayIsArray(errors) &&
16721708
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'errors'))) {
1673-
ArrayPrototypePush(keys, 'errors');
1709+
ArrayPrototypePush(keys, 'errors');
1710+
}
1711+
} catch {
1712+
// If errors is a getter that throws, we ignore the error.
16741713
}
16751714

16761715
stack = improveStack(stack, constructor, name, tag);
16771716

16781717
// Ignore the error message if it's contained in the stack.
1679-
let pos = (err.message && StringPrototypeIndexOf(stack, err.message)) || -1;
1718+
let pos = (message && StringPrototypeIndexOf(stack, message)) || -1;
16801719
if (pos !== -1)
1681-
pos += err.message.length;
1720+
pos += message.length;
16821721
// Wrap the error in brackets in case it has no stack trace.
16831722
const stackStart = StringPrototypeIndexOf(stack, '\n at', pos);
16841723
if (stackStart === -1) {

test/parallel/test-util-inspect.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3680,3 +3680,28 @@ ${error.stack.split('\n').slice(1).join('\n')}`,
36803680

36813681
assert.strictEqual(inspect(error), '[Error: foo\n [Error: bar\n [Circular *1]]]');
36823682
}
3683+
3684+
{
3685+
Object.defineProperty(Error, Symbol.hasInstance,
3686+
{ __proto__: null, value: common.mustNotCall(), configurable: true });
3687+
const error = new Error();
3688+
3689+
const throwingGetter = {
3690+
__proto__: null,
3691+
get() {
3692+
throw error;
3693+
},
3694+
configurable: true,
3695+
enumerable: true,
3696+
};
3697+
3698+
Object.defineProperties(error, {
3699+
name: throwingGetter,
3700+
stack: throwingGetter,
3701+
cause: throwingGetter,
3702+
});
3703+
3704+
assert.strictEqual(inspect(error), `[object Error] {\n stack: [Getter/Setter],\n name: [Getter],\n cause: [Getter]\n}`);
3705+
assert.match(inspect(DOMException.prototype), /^\[object DOMException\] \{/);
3706+
delete Error[Symbol.hasInstance];
3707+
}

0 commit comments

Comments
 (0)