Skip to content

Commit 63d5242

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 b9277a0 commit 63d5242

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,
@@ -1468,13 +1469,19 @@ function getDuplicateErrorFrameRanges(frames) {
14681469
}
14691470

14701471
function getStackString(ctx, error) {
1471-
if (error.stack) {
1472-
if (typeof error.stack === 'string') {
1473-
return error.stack;
1472+
let stack;
1473+
try {
1474+
stack = error.stack;
1475+
} catch {
1476+
// If stack is getter that throws, we ignore the error.
1477+
}
1478+
if (stack) {
1479+
if (typeof stack === 'string') {
1480+
return stack;
14741481
}
14751482
ctx.seen.push(error);
14761483
ctx.indentationLvl += 4;
1477-
const result = formatValue(ctx, error.stack);
1484+
const result = formatValue(ctx, stack);
14781485
ctx.indentationLvl -= 4;
14791486
ctx.seen.pop();
14801487
return `${ErrorPrototypeToString(error)}\n ${result}`;
@@ -1570,18 +1577,6 @@ function improveStack(stack, constructor, name, tag) {
15701577
return stack;
15711578
}
15721579

1573-
function removeDuplicateErrorKeys(ctx, keys, err, stack) {
1574-
if (!ctx.showHidden && keys.length !== 0) {
1575-
for (const name of ['name', 'message', 'stack']) {
1576-
const index = ArrayPrototypeIndexOf(keys, name);
1577-
// Only hide the property if it's a string and if it's part of the original stack
1578-
if (index !== -1 && (typeof err[name] !== 'string' || StringPrototypeIncludes(stack, err[name]))) {
1579-
ArrayPrototypeSplice(keys, index, 1);
1580-
}
1581-
}
1582-
}
1583-
}
1584-
15851580
function markNodeModules(ctx, line) {
15861581
let tempLine = '';
15871582
let lastPos = 0;
@@ -1664,28 +1659,72 @@ function safeGetCWD() {
16641659
}
16651660

16661661
function formatError(err, constructor, tag, ctx, keys) {
1667-
const name = err.name != null ? err.name : 'Error';
1668-
let stack = getStackString(ctx, err);
1662+
let message, name, stack;
1663+
try {
1664+
stack = getStackString(ctx, err);
1665+
} catch {
1666+
return ObjectPrototypeToString(err);
1667+
}
16691668

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

16721706
if ('cause' in err &&
16731707
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
16741708
ArrayPrototypePush(keys, 'cause');
16751709
}
16761710

16771711
// Print errors aggregated into AggregateError
1678-
if (ArrayIsArray(err.errors) &&
1712+
try {
1713+
const errors = err.errors;
1714+
if (ArrayIsArray(errors) &&
16791715
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'errors'))) {
1680-
ArrayPrototypePush(keys, 'errors');
1716+
ArrayPrototypePush(keys, 'errors');
1717+
}
1718+
} catch {
1719+
// If errors is a getter that throws, we ignore the error.
16811720
}
16821721

16831722
stack = improveStack(stack, constructor, name, tag);
16841723

16851724
// Ignore the error message if it's contained in the stack.
1686-
let pos = (err.message && StringPrototypeIndexOf(stack, err.message)) || -1;
1725+
let pos = (message && StringPrototypeIndexOf(stack, message)) || -1;
16871726
if (pos !== -1)
1688-
pos += err.message.length;
1727+
pos += message.length;
16891728
// Wrap the error in brackets in case it has no stack trace.
16901729
const stackStart = StringPrototypeIndexOf(stack, '\n at', pos);
16911730
if (stackStart === -1) {

test/parallel/test-util-inspect.js

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

37173717
assert.strictEqual(inspect(error), '[Error: foo\n [Error: bar\n [Circular *1]]]');
37183718
}
3719+
3720+
{
3721+
Object.defineProperty(Error, Symbol.hasInstance,
3722+
{ __proto__: null, value: common.mustNotCall(), configurable: true });
3723+
const error = new Error();
3724+
3725+
const throwingGetter = {
3726+
__proto__: null,
3727+
get() {
3728+
throw error;
3729+
},
3730+
configurable: true,
3731+
enumerable: true,
3732+
};
3733+
3734+
Object.defineProperties(error, {
3735+
name: throwingGetter,
3736+
stack: throwingGetter,
3737+
cause: throwingGetter,
3738+
});
3739+
3740+
assert.strictEqual(inspect(error), `[object Error] {\n stack: [Getter/Setter],\n name: [Getter],\n cause: [Getter]\n}`);
3741+
assert.match(inspect(DOMException.prototype), /^\[object DOMException\] \{/);
3742+
delete Error[Symbol.hasInstance];
3743+
}

0 commit comments

Comments
 (0)