Skip to content

Commit efab956

Browse files
committed
util: use more defensive code when inspecting error objects
1 parent b757a8f commit efab956

File tree

3 files changed

+85
-25
lines changed

3 files changed

+85
-25
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: 58 additions & 24 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,
@@ -1703,13 +1704,19 @@ function getDuplicateErrorFrameRanges(frames) {
17031704
}
17041705

17051706
function getStackString(ctx, error) {
1706-
if (error.stack) {
1707-
if (typeof error.stack === 'string') {
1708-
return error.stack;
1707+
let stack;
1708+
try {
1709+
stack = error.stack;
1710+
} catch {
1711+
// If stack is getter that throws, we ignore the error.
1712+
}
1713+
if (stack) {
1714+
if (typeof stack === 'string') {
1715+
return stack;
17091716
}
17101717
ctx.seen.push(error);
17111718
ctx.indentationLvl += 4;
1712-
const result = formatValue(ctx, error.stack);
1719+
const result = formatValue(ctx, stack);
17131720
ctx.indentationLvl -= 4;
17141721
ctx.seen.pop();
17151722
return `${ErrorPrototypeToString(error)}\n ${result}`;
@@ -1805,18 +1812,6 @@ function improveStack(stack, constructor, name, tag) {
18051812
return stack;
18061813
}
18071814

1808-
function removeDuplicateErrorKeys(ctx, keys, err, stack) {
1809-
if (!ctx.showHidden && keys.length !== 0) {
1810-
for (const name of ['name', 'message', 'stack']) {
1811-
const index = ArrayPrototypeIndexOf(keys, name);
1812-
// Only hide the property if it's a string and if it's part of the original stack
1813-
if (index !== -1 && (typeof err[name] !== 'string' || StringPrototypeIncludes(stack, err[name]))) {
1814-
ArrayPrototypeSplice(keys, index, 1);
1815-
}
1816-
}
1817-
}
1818-
}
1819-
18201815
function markNodeModules(ctx, line) {
18211816
let tempLine = '';
18221817
let lastPos = 0;
@@ -1899,28 +1894,67 @@ function safeGetCWD() {
18991894
}
19001895

19011896
function formatError(err, constructor, tag, ctx, keys) {
1902-
const name = err.name != null ? err.name : 'Error';
1903-
let stack = getStackString(ctx, err);
1904-
1905-
removeDuplicateErrorKeys(ctx, keys, err, stack);
1897+
let message, name, stack;
1898+
try {
1899+
stack = getStackString(ctx, err);
1900+
if (!ctx.showHidden && keys.length !== 0) {
1901+
const index = ArrayPrototypeIndexOf(keys, 'stack');
1902+
// Only hide the property if it's a string and if it's part of the original stack
1903+
if (index !== -1) {
1904+
ArrayPrototypeSplice(keys, index, 1);
1905+
}
1906+
}
1907+
} catch {
1908+
return ObjectPrototypeToString(err);
1909+
}
1910+
try {
1911+
message = err.message;
1912+
if (!ctx.showHidden && keys.length !== 0) {
1913+
const index = ArrayPrototypeIndexOf(keys, 'message');
1914+
// Only hide the property if it's a string and if it's part of the original stack
1915+
if (index !== -1 && (typeof message !== 'string' || StringPrototypeIncludes(stack, message))) {
1916+
ArrayPrototypeSplice(keys, index, 1);
1917+
}
1918+
}
1919+
} catch {
1920+
// If message is a getter that throws, we ignore the error.
1921+
}
1922+
try {
1923+
name = err.name;
1924+
if (!ctx.showHidden && keys.length !== 0) {
1925+
const index = ArrayPrototypeIndexOf(keys, 'name');
1926+
// Only hide the property if it's a string and if it's part of the original stack
1927+
if (index !== -1 && (typeof name !== 'string' || StringPrototypeIncludes(stack, name))) {
1928+
ArrayPrototypeSplice(keys, index, 1);
1929+
}
1930+
}
1931+
name ??= 'Error';
1932+
} catch {
1933+
name = 'Error';
1934+
}
19061935

19071936
if ('cause' in err &&
19081937
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
19091938
ArrayPrototypePush(keys, 'cause');
19101939
}
19111940

19121941
// Print errors aggregated into AggregateError
1913-
if (ArrayIsArray(err.errors) &&
1942+
try {
1943+
const errors = err.errors;
1944+
if (ArrayIsArray(errors) &&
19141945
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'errors'))) {
1915-
ArrayPrototypePush(keys, 'errors');
1946+
ArrayPrototypePush(keys, 'errors');
1947+
}
1948+
} catch {
1949+
// If errors is a getter that throws, we ignore the error.
19161950
}
19171951

19181952
stack = improveStack(stack, constructor, name, tag);
19191953

19201954
// Ignore the error message if it's contained in the stack.
1921-
let pos = (err.message && StringPrototypeIndexOf(stack, err.message)) || -1;
1955+
let pos = (message && StringPrototypeIndexOf(stack, message)) || -1;
19221956
if (pos !== -1)
1923-
pos += err.message.length;
1957+
pos += message.length;
19241958
// Wrap the error in brackets in case it has no stack trace.
19251959
const stackStart = StringPrototypeIndexOf(stack, '\n at', pos);
19261960
if (stackStart === -1) {

test/parallel/test-util-inspect.js

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

37063706
assert.strictEqual(inspect(error), '[Error: foo\n [Error: bar\n [Circular *1]]]');
37073707
}
3708+
3709+
{
3710+
Object.defineProperty(Error, Symbol.hasInstance,
3711+
{ __proto__: null, value: common.mustNotCall(), configurable: true });
3712+
const error = new Error();
3713+
3714+
const throwingGetter = {
3715+
__proto__: null,
3716+
get() {
3717+
throw error;
3718+
},
3719+
configurable: true,
3720+
enumerable: true,
3721+
};
3722+
3723+
Object.defineProperties(error, {
3724+
name: throwingGetter,
3725+
stack: throwingGetter,
3726+
cause: throwingGetter,
3727+
});
3728+
3729+
assert.strictEqual(inspect(error), `[object Error] {\n stack: [Getter/Setter],\n name: [Getter],\n cause: [Getter]\n}`);
3730+
assert.match(inspect(DOMException.prototype), /^\[object DOMException\] \{/);
3731+
delete Error[Symbol.hasInstance];
3732+
}

0 commit comments

Comments
 (0)