Skip to content

Commit 4d461e0

Browse files
committed
fix(util): handle non-own errors property in inspect
1 parent 05f8772 commit 4d461e0

File tree

2 files changed

+80
-61
lines changed

2 files changed

+80
-61
lines changed

lib/internal/util/inspect.js

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ function getUserOptions(ctx, isCrossContext) {
323323
ObjectSetPrototypeOf(ret, null);
324324
for (const key of ObjectKeys(ret)) {
325325
if ((typeof ret[key] === 'object' || typeof ret[key] === 'function') &&
326-
ret[key] !== null) {
326+
ret[key] !== null) {
327327
delete ret[key];
328328
}
329329
}
@@ -624,7 +624,7 @@ function highlightRegExp(regexpString) {
624624
i++;
625625
inClass = false;
626626
} else if (ch === '-' && regexpString[i - 1] !== '[' &&
627-
i + 1 < regexpString.length && regexpString[i + 1] !== ']') {
627+
i + 1 < regexpString.length && regexpString[i + 1] !== ']') {
628628
writeDepth('-', 1, 1);
629629
} else {
630630
write(ch);
@@ -799,7 +799,7 @@ function strEscape(str) {
799799
if (!StringPrototypeIncludes(str, '"')) {
800800
singleQuote = -1;
801801
} else if (!StringPrototypeIncludes(str, '`') &&
802-
!StringPrototypeIncludes(str, '${')) {
802+
!StringPrototypeIncludes(str, '${')) {
803803
singleQuote = -2;
804804
}
805805
if (singleQuote !== 39) {
@@ -821,9 +821,9 @@ function strEscape(str) {
821821
for (let i = 0; i < str.length; i++) {
822822
const point = StringPrototypeCharCodeAt(str, i);
823823
if (point === singleQuote ||
824-
point === 92 ||
825-
point < 32 ||
826-
(point > 126 && point < 160)) {
824+
point === 92 ||
825+
point < 32 ||
826+
(point > 126 && point < 160)) {
827827
if (last === i) {
828828
result += meta[point];
829829
} else {
@@ -922,12 +922,12 @@ function getConstructorName(obj, ctx, recurseTimes, protoProps) {
922922
}
923923
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
924924
if (descriptor !== undefined &&
925-
typeof descriptor.value === 'function' &&
926-
descriptor.value.name !== '' &&
927-
isInstanceof(tmp, descriptor.value)) {
925+
typeof descriptor.value === 'function' &&
926+
descriptor.value.name !== '' &&
927+
isInstanceof(tmp, descriptor.value)) {
928928
if (protoProps !== undefined &&
929-
(firstProto !== obj ||
930-
!builtInObjects.has(descriptor.value.name))) {
929+
(firstProto !== obj ||
930+
!builtInObjects.has(descriptor.value.name))) {
931931
addPrototypeProperties(
932932
ctx, tmp, firstProto || tmp, recurseTimes, protoProps);
933933
}
@@ -981,8 +981,8 @@ function addPrototypeProperties(ctx, main, obj, recurseTimes, output) {
981981
// Stop as soon as a built-in object type is detected.
982982
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
983983
if (descriptor !== undefined &&
984-
typeof descriptor.value === 'function' &&
985-
builtInObjects.has(descriptor.value.name)) {
984+
typeof descriptor.value === 'function' &&
985+
builtInObjects.has(descriptor.value.name)) {
986986
return;
987987
}
988988
}
@@ -998,8 +998,8 @@ function addPrototypeProperties(ctx, main, obj, recurseTimes, output) {
998998
for (const key of keys) {
999999
// Ignore the `constructor` property and keys that exist on layers above.
10001000
if (key === 'constructor' ||
1001-
ObjectPrototypeHasOwnProperty(main, key) ||
1002-
(depth !== 0 && keySet.has(key))) {
1001+
ObjectPrototypeHasOwnProperty(main, key) ||
1002+
(depth !== 0 && keySet.has(key))) {
10031003
continue;
10041004
}
10051005
const desc = ObjectGetOwnPropertyDescriptor(obj, key);
@@ -1016,9 +1016,9 @@ function addPrototypeProperties(ctx, main, obj, recurseTimes, output) {
10161016
}
10171017
}
10181018
ArrayPrototypePop(ctx.seen);
1019-
// Limit the inspection to up to three prototype layers. Using `recurseTimes`
1020-
// is not a good choice here, because it's as if the properties are declared
1021-
// on the current object from the users perspective.
1019+
// Limit the inspection to up to three prototype layers. Using `recurseTimes`
1020+
// is not a good choice here, because it's as if the properties are declared
1021+
// on the current object from the users perspective.
10221022
} while (++depth !== 3);
10231023
}
10241024

@@ -1065,7 +1065,7 @@ function getKeys(value, showHidden) {
10651065
keys = ObjectKeys(value);
10661066
} catch (err) {
10671067
assert(isNativeError(err) && err.name === 'ReferenceError' &&
1068-
isModuleNamespaceObject(value));
1068+
isModuleNamespaceObject(value));
10691069
keys = ObjectGetOwnPropertyNames(value);
10701070
}
10711071
if (symbols.length !== 0) {
@@ -1108,8 +1108,8 @@ function formatProxy(ctx, proxy, recurseTimes) {
11081108
function formatValue(ctx, value, recurseTimes, typedArray) {
11091109
// Primitive types cannot have properties.
11101110
if (typeof value !== 'object' &&
1111-
typeof value !== 'function' &&
1112-
!isUndetectableObject(value)) {
1111+
typeof value !== 'function' &&
1112+
!isUndetectableObject(value)) {
11131113
return formatPrimitive(ctx.stylize, value, ctx);
11141114
}
11151115
if (value === null) {
@@ -1136,10 +1136,10 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
11361136
if (ctx.customInspect) {
11371137
const maybeCustom = value[customInspectSymbol];
11381138
if (typeof maybeCustom === 'function' &&
1139-
// Filter out the util module, its inspect function is special.
1140-
maybeCustom !== inspect &&
1141-
// Also filter out any prototype objects using the circular check.
1142-
ObjectGetOwnPropertyDescriptor(value, 'constructor')?.value?.prototype !== value) {
1139+
// Filter out the util module, its inspect function is special.
1140+
maybeCustom !== inspect &&
1141+
// Also filter out any prototype objects using the circular check.
1142+
ObjectGetOwnPropertyDescriptor(value, 'constructor')?.value?.prototype !== value) {
11431143
// This makes sure the recurseTimes are reported as before while using
11441144
// a counter internally.
11451145
const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
@@ -1207,12 +1207,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
12071207
// Only list the tag in case it's non-enumerable / not an own property.
12081208
// Otherwise we'd print this twice.
12091209
if (typeof tag !== 'string' ||
1210-
(tag !== '' &&
1210+
(tag !== '' &&
12111211
(ctx.showHidden ?
12121212
ObjectPrototypeHasOwnProperty :
12131213
ObjectPrototypePropertyIsEnumerable)(
1214-
value, SymbolToStringTag,
1215-
))) {
1214+
value, SymbolToStringTag,
1215+
))) {
12161216
tag = '';
12171217
}
12181218
let base = '';
@@ -1324,7 +1324,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
13241324
base = `${prefix}${base}`;
13251325
base = ctx.stylize(base, 'regexp');
13261326
if ((keys.length === 0 && protoProps === undefined) ||
1327-
(recurseTimes > ctx.depth && ctx.depth !== null)) {
1327+
(recurseTimes > ctx.depth && ctx.depth !== null)) {
13281328
return base;
13291329
}
13301330
} else if (isDate(value)) {
@@ -1353,7 +1353,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
13531353
formatter = formatArrayBuffer;
13541354
} else if (keys.length === 0 && protoProps === undefined) {
13551355
return prefix +
1356-
`{ [byteLength]: ${formatNumber(ctx.stylize, value.byteLength, false)} }`;
1356+
`{ [byteLength]: ${formatNumber(ctx.stylize, value.byteLength, false)} }`;
13571357
}
13581358
braces[0] = `${prefix}{`;
13591359
extraKeys = ['byteLength'];
@@ -1555,7 +1555,7 @@ function getFunctionBase(ctx, value, constructor, tag) {
15551555
const slice = StringPrototypeSlice(stringified, 5, -1);
15561556
const bracketIndex = StringPrototypeIndexOf(slice, '{');
15571557
if (bracketIndex !== -1 &&
1558-
(!StringPrototypeIncludes(StringPrototypeSlice(slice, 0, bracketIndex), '(') ||
1558+
(!StringPrototypeIncludes(StringPrototypeSlice(slice, 0, bracketIndex), '(') ||
15591559
// Slow path to guarantee that it's indeed a class.
15601560
RegExpPrototypeExec(classRegExp, RegExpPrototypeSymbolReplace(stripCommentsRegExp, slice)) !== null)
15611561
) {
@@ -1679,7 +1679,7 @@ function getDuplicateErrorFrameRanges(frames) {
16791679

16801680
let duplicateRanges = 0;
16811681

1682-
for (let nextStart = i + range; /* ignored */ ; nextStart += range) {
1682+
for (let nextStart = i + range; /* ignored */; nextStart += range) {
16831683
let equalFrames = 0;
16841684
for (let j = 0; j < range; j++) {
16851685
if (frames[i + j] !== frames[nextStart + j]) {
@@ -1802,13 +1802,13 @@ function improveStack(stack, constructor, name, tag) {
18021802
}
18031803

18041804
if (constructor === null ||
1805-
(StringPrototypeEndsWith(name, 'Error') &&
1805+
(StringPrototypeEndsWith(name, 'Error') &&
18061806
StringPrototypeStartsWith(stack, name) &&
18071807
(stack.length === len || stack[len] === ':' || stack[len] === '\n'))) {
18081808
let fallback = 'Error';
18091809
if (constructor === null) {
18101810
const start = RegExpPrototypeExec(/^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/, stack) ||
1811-
RegExpPrototypeExec(/^([a-z_A-Z0-9-]*Error)$/, stack);
1811+
RegExpPrototypeExec(/^([a-z_A-Z0-9-]*Error)$/, stack);
18121812
fallback = (start?.[1]) || '';
18131813
len = fallback.length;
18141814
fallback ||= 'Error';
@@ -1956,7 +1956,7 @@ function formatError(err, constructor, tag, ctx, keys) {
19561956
name ??= 'Error';
19571957

19581958
if ('cause' in err &&
1959-
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
1959+
(keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
19601960
ArrayPrototypePush(keys, 'cause');
19611961
}
19621962

@@ -2051,7 +2051,7 @@ function groupArrayElements(ctx, output, value) {
20512051
// entry is longer than 1/5 of all other entries combined). Otherwise the
20522052
// space in-between small entries would be enormous.
20532053
if (actualMax * 3 + ctx.indentationLvl < ctx.breakLength &&
2054-
(totalLength / actualMax > 5 || maxLength <= 6)) {
2054+
(totalLength / actualMax > 5 || maxLength <= 6)) {
20552055

20562056
const approxCharHeights = 2.5;
20572057
const averageBias = MathSqrt(actualMax - totalLength / output.length);
@@ -2115,9 +2115,9 @@ function groupArrayElements(ctx, output, value) {
21152115
}
21162116
if (order === StringPrototypePadStart) {
21172117
const padding = maxLineLength[j - i] +
2118-
output[j].length -
2119-
dataLen[j] -
2120-
separatorSpace;
2118+
output[j].length -
2119+
dataLen[j] -
2120+
separatorSpace;
21212121
str += StringPrototypePadStart(output[j], padding, ' ');
21222122
} else {
21232123
str += output[j];
@@ -2137,7 +2137,7 @@ function handleMaxCallStackSize(ctx, err, constructorName, indentationLvl) {
21372137
ctx.indentationLvl = indentationLvl;
21382138
return ctx.stylize(
21392139
`[${constructorName}: Inspection interrupted ` +
2140-
'prematurely. Maximum call stack size exceeded.]',
2140+
'prematurely. Maximum call stack size exceeded.]',
21412141
'special',
21422142
);
21432143
}
@@ -2194,11 +2194,9 @@ function formatNumber(fn, number, numericSeparator) {
21942194
const integerPart = StringPrototypeSlice(numberString, 0, decimalIndex);
21952195
const fractionalPart = StringPrototypeSlice(numberString, decimalIndex + 1);
21962196

2197-
return fn(`${
2198-
addNumericSeparator(integerPart)
2199-
}.${
2200-
addNumericSeparatorEnd(fractionalPart)
2201-
}`, 'number');
2197+
return fn(`${addNumericSeparator(integerPart)
2198+
}.${addNumericSeparatorEnd(fractionalPart)
2199+
}`, 'number');
22022200
}
22032201

22042202
function formatBigInt(fn, bigint, numericSeparator) {
@@ -2218,11 +2216,11 @@ function formatPrimitive(fn, value, ctx) {
22182216
trailer = `... ${remaining} more character${remaining > 1 ? 's' : ''}`;
22192217
}
22202218
if (ctx.compact !== true &&
2221-
// We do not support handling unicode characters width with
2222-
// the readline getStringWidth function as there are
2223-
// performance implications.
2224-
value.length > kMinLineLength &&
2225-
value.length > ctx.breakLength - ctx.indentationLvl - 4) {
2219+
// We do not support handling unicode characters width with
2220+
// the readline getStringWidth function as there are
2221+
// performance implications.
2222+
value.length > kMinLineLength &&
2223+
value.length > ctx.breakLength - ctx.indentationLvl - 4) {
22262224
return ArrayPrototypeJoin(
22272225
ArrayPrototypeMap(
22282226
RegExpPrototypeSymbolSplit(/(?<=\n)/, value),
@@ -2250,7 +2248,7 @@ function formatNamespaceObject(keys, ctx, value, recurseTimes) {
22502248
for (let i = 0; i < keys.length; i++) {
22512249
try {
22522250
output[i] = formatProperty(ctx, value, recurseTimes, keys[i],
2253-
kObjectType);
2251+
kObjectType);
22542252
} catch (err) {
22552253
assert(isNativeError(err) && err.name === 'ReferenceError');
22562254
// Use the existing functionality. This makes sure the indentation and
@@ -2262,7 +2260,7 @@ function formatNamespaceObject(keys, ctx, value, recurseTimes) {
22622260
// We have to find the last whitespace and have to replace that value as
22632261
// it will be visualized as a regular string.
22642262
output[i] = StringPrototypeSlice(output[i], 0, pos + 1) +
2265-
ctx.stylize('<uninitialized>', 'special');
2263+
ctx.stylize('<uninitialized>', 'special');
22662264
}
22672265
}
22682266
// Reset the keys to an empty array. This prevents duplicated inspection.
@@ -2525,10 +2523,11 @@ function formatExtraProperties(ctx, value, recurseTimes, key, typedArray) {
25252523
}
25262524

25272525
function formatProperty(ctx, value, recurseTimes, key, type, desc,
2528-
original = value) {
2526+
original = value) {
25292527
let name, str;
25302528
let extra = ' ';
2531-
desc ??= ObjectGetOwnPropertyDescriptor(value, key);
2529+
desc ??= ObjectGetOwnPropertyDescriptor(value, key) ||
2530+
{ value: value[key], enumerable: true };
25322531
if (desc.value !== undefined) {
25332532
const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3;
25342533
ctx.indentationLvl += diff;
@@ -2542,8 +2541,8 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc,
25422541
const s = ctx.stylize;
25432542
const sp = 'special';
25442543
if (ctx.getters && (ctx.getters === true ||
2545-
(ctx.getters === 'get' && desc.set === undefined) ||
2546-
(ctx.getters === 'set' && desc.set !== undefined))) {
2544+
(ctx.getters === 'get' && desc.set === undefined) ||
2545+
(ctx.getters === 'set' && desc.set !== undefined))) {
25472546
ctx.indentationLvl += 2;
25482547
try {
25492548
const tmp = FunctionPrototypeCall(desc.get, original);
@@ -2641,12 +2640,12 @@ function reduceToSingleString(
26412640
// `ctx.compact`, as long as the properties are smaller than
26422641
// `ctx.breakLength`.
26432642
if (ctx.currentDepth - recurseTimes < ctx.compact &&
2644-
entries === output.length) {
2643+
entries === output.length) {
26452644
// Line up all entries on a single line in case the entries do not
26462645
// exceed `breakLength`. Add 10 as constant to start next to all other
26472646
// factors that may reduce `breakLength`.
26482647
const start = output.length + ctx.indentationLvl +
2649-
braces[0].length + base.length + 10;
2648+
braces[0].length + base.length + 10;
26502649
if (isBelowBreakLength(ctx, output, start, base)) {
26512650
const joinedOutput = join(output, ', ');
26522651
if (!StringPrototypeIncludes(joinedOutput, '\n')) {
@@ -2743,7 +2742,7 @@ function tryStringify(arg) {
27432742
}
27442743
}
27452744
if (err.name === 'TypeError' &&
2746-
firstErrorLine(err) === CIRCULAR_ERROR_MESSAGE) {
2745+
firstErrorLine(err) === CIRCULAR_ERROR_MESSAGE) {
27472746
return '[Circular]';
27482747
}
27492748
throw err;
@@ -2800,8 +2799,8 @@ function formatWithOptionsInternal(inspectOptions, args) {
28002799
} else if (typeof tempArg === 'bigint') {
28012800
tempStr = formatBigIntNoColor(tempArg, inspectOptions);
28022801
} else if (typeof tempArg !== 'object' ||
2803-
tempArg === null ||
2804-
!hasBuiltInToString(tempArg)) {
2802+
tempArg === null ||
2803+
!hasBuiltInToString(tempArg)) {
28052804
tempStr = String(tempArg);
28062805
} else {
28072806
tempStr = inspect(tempArg, {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const util = require('util');
5+
6+
// Test that inspecting an object with a non-own 'errors' property does not crash.
7+
// Regression test for https://github.com/nodejs/node/issues/60808 (conceptually)
8+
9+
class MockError extends Error {
10+
constructor() {
11+
super('foo');
12+
}
13+
// 'errors' is a getter on the prototype, not an own property
14+
get errors() {
15+
return ['bar'];
16+
}
17+
}
18+
19+
const err = new MockError();
20+
assert.doesNotThrow(() => util.inspect(err));

0 commit comments

Comments
 (0)