mirror of
https://github.com/nodejs/node.git
synced 2025-12-28 07:50:41 +00:00
util: use more defensive code when inspecting error objects
PR-URL: https://github.com/nodejs/node/pull/60139 Fixes: https://github.com/nodejs/node/issues/60107 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
cec1bd5498
commit
2fb82c8c28
@ -8,6 +8,7 @@ const {
|
||||
Error,
|
||||
ErrorCaptureStackTrace,
|
||||
FunctionPrototypeCall,
|
||||
FunctionPrototypeSymbolHasInstance,
|
||||
NumberParseInt,
|
||||
ObjectDefineProperties,
|
||||
ObjectDefineProperty,
|
||||
@ -96,7 +97,7 @@ function isError(e) {
|
||||
// An error could be an instance of Error while not being a native error
|
||||
// or could be from a different realm and not be instance of Error but still
|
||||
// be a native error.
|
||||
return isNativeError(e) || e instanceof Error;
|
||||
return isNativeError(e) || FunctionPrototypeSymbolHasInstance(Error, e);
|
||||
}
|
||||
|
||||
// Keep a list of deprecation codes that have been warned on so we only warn on
|
||||
|
||||
@ -72,6 +72,7 @@ const {
|
||||
ObjectPrototype,
|
||||
ObjectPrototypeHasOwnProperty,
|
||||
ObjectPrototypePropertyIsEnumerable,
|
||||
ObjectPrototypeToString,
|
||||
ObjectSeal,
|
||||
ObjectSetPrototypeOf,
|
||||
Promise,
|
||||
@ -1720,13 +1721,19 @@ function getDuplicateErrorFrameRanges(frames) {
|
||||
}
|
||||
|
||||
function getStackString(ctx, error) {
|
||||
if (error.stack) {
|
||||
if (typeof error.stack === 'string') {
|
||||
return error.stack;
|
||||
let stack;
|
||||
try {
|
||||
stack = error.stack;
|
||||
} catch {
|
||||
// If stack is getter that throws, we ignore the error.
|
||||
}
|
||||
if (stack) {
|
||||
if (typeof stack === 'string') {
|
||||
return stack;
|
||||
}
|
||||
ctx.seen.push(error);
|
||||
ctx.indentationLvl += 4;
|
||||
const result = formatValue(ctx, error.stack);
|
||||
const result = formatValue(ctx, stack);
|
||||
ctx.indentationLvl -= 4;
|
||||
ctx.seen.pop();
|
||||
return `${ErrorPrototypeToString(error)}\n ${result}`;
|
||||
@ -1822,18 +1829,6 @@ function improveStack(stack, constructor, name, tag) {
|
||||
return stack;
|
||||
}
|
||||
|
||||
function removeDuplicateErrorKeys(ctx, keys, err, stack) {
|
||||
if (!ctx.showHidden && keys.length !== 0) {
|
||||
for (const name of ['name', 'message', 'stack']) {
|
||||
const index = ArrayPrototypeIndexOf(keys, name);
|
||||
// Only hide the property if it's a string and if it's part of the original stack
|
||||
if (index !== -1 && (typeof err[name] !== 'string' || StringPrototypeIncludes(stack, err[name]))) {
|
||||
ArrayPrototypeSplice(keys, index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function markNodeModules(ctx, line) {
|
||||
let tempLine = '';
|
||||
let lastPos = 0;
|
||||
@ -1916,10 +1911,49 @@ function safeGetCWD() {
|
||||
}
|
||||
|
||||
function formatError(err, constructor, tag, ctx, keys) {
|
||||
const name = err.name != null ? err.name : 'Error';
|
||||
let stack = getStackString(ctx, err);
|
||||
let message, name, stack;
|
||||
try {
|
||||
stack = getStackString(ctx, err);
|
||||
} catch {
|
||||
return ObjectPrototypeToString(err);
|
||||
}
|
||||
|
||||
removeDuplicateErrorKeys(ctx, keys, err, stack);
|
||||
let messageIsGetterThatThrows = false;
|
||||
try {
|
||||
message = err.message;
|
||||
} catch {
|
||||
messageIsGetterThatThrows = true;
|
||||
}
|
||||
let nameIsGetterThatThrows = false;
|
||||
try {
|
||||
name = err.name;
|
||||
} catch {
|
||||
nameIsGetterThatThrows = true;
|
||||
}
|
||||
|
||||
if (!ctx.showHidden && keys.length !== 0) {
|
||||
const index = ArrayPrototypeIndexOf(keys, 'stack');
|
||||
if (index !== -1) {
|
||||
ArrayPrototypeSplice(keys, index, 1);
|
||||
}
|
||||
|
||||
if (!messageIsGetterThatThrows) {
|
||||
const index = ArrayPrototypeIndexOf(keys, 'message');
|
||||
// Only hide the property if it's a string and if it's part of the original stack
|
||||
if (index !== -1 && (typeof message !== 'string' || StringPrototypeIncludes(stack, message))) {
|
||||
ArrayPrototypeSplice(keys, index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nameIsGetterThatThrows) {
|
||||
const index = ArrayPrototypeIndexOf(keys, 'name');
|
||||
// Only hide the property if it's a string and if it's part of the original stack
|
||||
if (index !== -1 && (typeof name !== 'string' || StringPrototypeIncludes(stack, name))) {
|
||||
ArrayPrototypeSplice(keys, index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
name ??= 'Error';
|
||||
|
||||
if ('cause' in err &&
|
||||
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
|
||||
@ -1927,17 +1961,22 @@ function formatError(err, constructor, tag, ctx, keys) {
|
||||
}
|
||||
|
||||
// Print errors aggregated into AggregateError
|
||||
if (ArrayIsArray(err.errors) &&
|
||||
try {
|
||||
const errors = err.errors;
|
||||
if (ArrayIsArray(errors) &&
|
||||
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'errors'))) {
|
||||
ArrayPrototypePush(keys, 'errors');
|
||||
ArrayPrototypePush(keys, 'errors');
|
||||
}
|
||||
} catch {
|
||||
// If errors is a getter that throws, we ignore the error.
|
||||
}
|
||||
|
||||
stack = improveStack(stack, constructor, name, tag);
|
||||
|
||||
// Ignore the error message if it's contained in the stack.
|
||||
let pos = (err.message && StringPrototypeIndexOf(stack, err.message)) || -1;
|
||||
let pos = (message && StringPrototypeIndexOf(stack, message)) || -1;
|
||||
if (pos !== -1)
|
||||
pos += err.message.length;
|
||||
pos += message.length;
|
||||
// Wrap the error in brackets in case it has no stack trace.
|
||||
const stackStart = StringPrototypeIndexOf(stack, '\n at', pos);
|
||||
if (stackStart === -1) {
|
||||
|
||||
@ -3716,3 +3716,28 @@ ${error.stack.split('\n').slice(1).join('\n')}`,
|
||||
|
||||
assert.strictEqual(inspect(error), '[Error: foo\n [Error: bar\n [Circular *1]]]');
|
||||
}
|
||||
|
||||
{
|
||||
Object.defineProperty(Error, Symbol.hasInstance,
|
||||
{ __proto__: null, value: common.mustNotCall(), configurable: true });
|
||||
const error = new Error();
|
||||
|
||||
const throwingGetter = {
|
||||
__proto__: null,
|
||||
get() {
|
||||
throw error;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
};
|
||||
|
||||
Object.defineProperties(error, {
|
||||
name: throwingGetter,
|
||||
stack: throwingGetter,
|
||||
cause: throwingGetter,
|
||||
});
|
||||
|
||||
assert.strictEqual(inspect(error), `[object Error] {\n stack: [Getter/Setter],\n name: [Getter],\n cause: [Getter]\n}`);
|
||||
assert.match(inspect(DOMException.prototype), /^\[object DOMException\] \{/);
|
||||
delete Error[Symbol.hasInstance];
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user