Skip to content

Commit 4a86016

Browse files
committed
util: use more defensive code when inspecting error objects
PR-URL: #60139 Fixes: #60107 Reviewed-By: Chengzhong Wu <[email protected]>
1 parent f437204 commit 4a86016

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,
@@ -1720,13 +1721,19 @@ function getDuplicateErrorFrameRanges(frames) {
17201721
}
17211722

17221723
function getStackString(ctx, error) {
1723-
if (error.stack) {
1724-
if (typeof error.stack === 'string') {
1725-
return error.stack;
1724+
let stack;
1725+
try {
1726+
stack = error.stack;
1727+
} catch {
1728+
// If stack is getter that throws, we ignore the error.
1729+
}
1730+
if (stack) {
1731+
if (typeof stack === 'string') {
1732+
return stack;
17261733
}
17271734
ctx.seen.push(error);
17281735
ctx.indentationLvl += 4;
1729-
const result = formatValue(ctx, error.stack);
1736+
const result = formatValue(ctx, stack);
17301737
ctx.indentationLvl -= 4;
17311738
ctx.seen.pop();
17321739
return `${ErrorPrototypeToString(error)}\n ${result}`;
@@ -1822,18 +1829,6 @@ function improveStack(stack, constructor, name, tag) {
18221829
return stack;
18231830
}
18241831

1825-
function removeDuplicateErrorKeys(ctx, keys, err, stack) {
1826-
if (!ctx.showHidden && keys.length !== 0) {
1827-
for (const name of ['name', 'message', 'stack']) {
1828-
const index = ArrayPrototypeIndexOf(keys, name);
1829-
// Only hide the property if it's a string and if it's part of the original stack
1830-
if (index !== -1 && (typeof err[name] !== 'string' || StringPrototypeIncludes(stack, err[name]))) {
1831-
ArrayPrototypeSplice(keys, index, 1);
1832-
}
1833-
}
1834-
}
1835-
}
1836-
18371832
function markNodeModules(ctx, line) {
18381833
let tempLine = '';
18391834
let lastPos = 0;
@@ -1916,28 +1911,72 @@ function safeGetCWD() {
19161911
}
19171912

19181913
function formatError(err, constructor, tag, ctx, keys) {
1919-
const name = err.name != null ? err.name : 'Error';
1920-
let stack = getStackString(ctx, err);
1914+
let message, name, stack;
1915+
try {
1916+
stack = getStackString(ctx, err);
1917+
} catch {
1918+
return ObjectPrototypeToString(err);
1919+
}
19211920

1922-
removeDuplicateErrorKeys(ctx, keys, err, stack);
1921+
let messageIsGetterThatThrows = false;
1922+
try {
1923+
message = err.message;
1924+
} catch {
1925+
messageIsGetterThatThrows = true;
1926+
}
1927+
let nameIsGetterThatThrows = false;
1928+
try {
1929+
name = err.name;
1930+
} catch {
1931+
nameIsGetterThatThrows = true;
1932+
}
1933+
1934+
if (!ctx.showHidden && keys.length !== 0) {
1935+
const index = ArrayPrototypeIndexOf(keys, 'stack');
1936+
if (index !== -1) {
1937+
ArrayPrototypeSplice(keys, index, 1);
1938+
}
1939+
1940+
if (!messageIsGetterThatThrows) {
1941+
const index = ArrayPrototypeIndexOf(keys, 'message');
1942+
// Only hide the property if it's a string and if it's part of the original stack
1943+
if (index !== -1 && (typeof message !== 'string' || StringPrototypeIncludes(stack, message))) {
1944+
ArrayPrototypeSplice(keys, index, 1);
1945+
}
1946+
}
1947+
1948+
if (!nameIsGetterThatThrows) {
1949+
const index = ArrayPrototypeIndexOf(keys, 'name');
1950+
// Only hide the property if it's a string and if it's part of the original stack
1951+
if (index !== -1 && (typeof name !== 'string' || StringPrototypeIncludes(stack, name))) {
1952+
ArrayPrototypeSplice(keys, index, 1);
1953+
}
1954+
}
1955+
}
1956+
name ??= 'Error';
19231957

19241958
if ('cause' in err &&
19251959
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
19261960
ArrayPrototypePush(keys, 'cause');
19271961
}
19281962

19291963
// Print errors aggregated into AggregateError
1930-
if (ArrayIsArray(err.errors) &&
1964+
try {
1965+
const errors = err.errors;
1966+
if (ArrayIsArray(errors) &&
19311967
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'errors'))) {
1932-
ArrayPrototypePush(keys, 'errors');
1968+
ArrayPrototypePush(keys, 'errors');
1969+
}
1970+
} catch {
1971+
// If errors is a getter that throws, we ignore the error.
19331972
}
19341973

19351974
stack = improveStack(stack, constructor, name, tag);
19361975

19371976
// Ignore the error message if it's contained in the stack.
1938-
let pos = (err.message && StringPrototypeIndexOf(stack, err.message)) || -1;
1977+
let pos = (message && StringPrototypeIndexOf(stack, message)) || -1;
19391978
if (pos !== -1)
1940-
pos += err.message.length;
1979+
pos += message.length;
19411980
// Wrap the error in brackets in case it has no stack trace.
19421981
const stackStart = StringPrototypeIndexOf(stack, '\n at', pos);
19431982
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)