Skip to content

Commit ca5956c

Browse files
authored
util: safely inspect getter errors whose message throws
PR-URL: #60684 Fixes: #60683 Reviewed-By: Antoine du Hamel <[email protected]>
1 parent ce29481 commit ca5956c

File tree

2 files changed

+258
-26
lines changed

2 files changed

+258
-26
lines changed

lib/internal/util/inspect.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2544,9 +2544,9 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc,
25442544
if (ctx.getters && (ctx.getters === true ||
25452545
(ctx.getters === 'get' && desc.set === undefined) ||
25462546
(ctx.getters === 'set' && desc.set !== undefined))) {
2547+
ctx.indentationLvl += 2;
25472548
try {
25482549
const tmp = FunctionPrototypeCall(desc.get, original);
2549-
ctx.indentationLvl += 2;
25502550
if (tmp === null) {
25512551
str = `${s(`[${label}:`, sp)} ${s('null', 'null')}${s(']', sp)}`;
25522552
} else if (typeof tmp === 'object') {
@@ -2555,11 +2555,11 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc,
25552555
const primitive = formatPrimitive(s, tmp, ctx);
25562556
str = `${s(`[${label}:`, sp)} ${primitive}${s(']', sp)}`;
25572557
}
2558-
ctx.indentationLvl -= 2;
25592558
} catch (err) {
2560-
const message = `<Inspection threw (${err.message})>`;
2559+
const message = `<Inspection threw (${formatValue(ctx, err, recurseTimes)})>`;
25612560
str = `${s(`[${label}:`, sp)} ${message}${s(']', sp)}`;
25622561
}
2562+
ctx.indentationLvl -= 2;
25632563
} else {
25642564
str = ctx.stylize(`[${label}]`, sp);
25652565
}

test/parallel/test-util-inspect.js

Lines changed: 255 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,9 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324');
690690

691691
{
692692
const tmp = Error.stackTraceLimit;
693-
Error.stackTraceLimit = 0;
693+
// Force stackTraceLimit = 0 for this test, but make it non-enumerable
694+
// so it doesn't appear in inspect() output when inspecting Error in other tests.
695+
Object.defineProperty(Error, 'stackTraceLimit', { value: 0, enumerable: false });
694696
const err = new Error('foo');
695697
const err2 = new Error('foo\nbar');
696698
assert.strictEqual(util.inspect(err, { compact: true }), '[Error: foo]');
@@ -2527,14 +2529,10 @@ assert.strictEqual(
25272529
set foo(val) { foo = val; },
25282530
get inc() { return ++foo; }
25292531
};
2530-
const thrower = { get foo() { throw new Error('Oops'); } };
25312532
assert.strictEqual(
25322533
inspect(get, { getters: true, colors: true }),
25332534
'{ foo: \u001b[36m[Getter:\u001b[39m ' +
25342535
'\u001b[33m1\u001b[39m\u001b[36m]\u001b[39m }');
2535-
assert.strictEqual(
2536-
inspect(thrower, { getters: true }),
2537-
'{ foo: [Getter: <Inspection threw (Oops)>] }');
25382536
assert.strictEqual(
25392537
inspect(getset, { getters: true }),
25402538
'{ foo: [Getter/Setter: 1], inc: [Getter: 2] }');
@@ -2551,6 +2549,239 @@ assert.strictEqual(
25512549
"'foobar', { x: 1 } },\n inc: [Getter: NaN]\n}");
25522550
}
25532551

2552+
// Property getter throwing an error.
2553+
{
2554+
const error = new Error('Oops');
2555+
error.stack = [
2556+
'Error: Oops',
2557+
' at get foo (/foo/node_modules/foo.js:2:7)',
2558+
' at get bar (/foo/node_modules/bar.js:827:30)',
2559+
].join('\n');
2560+
2561+
const thrower = {
2562+
get foo() { throw error; }
2563+
};
2564+
2565+
assert.strictEqual(
2566+
inspect(thrower, { getters: true }),
2567+
'{\n' +
2568+
' foo: [Getter: <Inspection threw (Error: Oops\n' +
2569+
' at get foo (/foo/node_modules/foo.js:2:7)\n' +
2570+
' at get bar (/foo/node_modules/bar.js:827:30))>]\n' +
2571+
'}',
2572+
);
2573+
};
2574+
2575+
// Property getter throwing an error with getters that throws.
2576+
// https://github.com/nodejs/node/issues/60683
2577+
{
2578+
const badError = new Error();
2579+
2580+
const innerError = new Error('Oops');
2581+
innerError.stack = [
2582+
'Error: Oops',
2583+
' at get foo (/foo/node_modules/foo.js:2:7)',
2584+
' at get bar (/foo/node_modules/bar.js:827:30)',
2585+
].join('\n');
2586+
2587+
const throwingGetter = {
2588+
__proto__: null,
2589+
get() {
2590+
throw innerError;
2591+
},
2592+
configurable: true,
2593+
enumerable: true,
2594+
};
2595+
2596+
Object.defineProperties(badError, {
2597+
name: throwingGetter,
2598+
message: throwingGetter,
2599+
stack: throwingGetter,
2600+
cause: throwingGetter,
2601+
});
2602+
2603+
const thrower = {
2604+
get foo() { throw badError; }
2605+
};
2606+
2607+
assert.strictEqual(
2608+
inspect(thrower, { getters: true }),
2609+
'{\n' +
2610+
' foo: [Getter: <Inspection threw ([object Error] {\n' +
2611+
' stack: [Getter/Setter: <Inspection threw (Error: Oops\n' +
2612+
' at get foo (/foo/node_modules/foo.js:2:7)\n' +
2613+
' at get bar (/foo/node_modules/bar.js:827:30))>],\n' +
2614+
' name: [Getter: <Inspection threw (Error: Oops\n' +
2615+
' at get foo (/foo/node_modules/foo.js:2:7)\n' +
2616+
' at get bar (/foo/node_modules/bar.js:827:30))>],\n' +
2617+
' message: [Getter: <Inspection threw (Error: Oops\n' +
2618+
' at get foo (/foo/node_modules/foo.js:2:7)\n' +
2619+
' at get bar (/foo/node_modules/bar.js:827:30))>],\n' +
2620+
' cause: [Getter: <Inspection threw (Error: Oops\n' +
2621+
' at get foo (/foo/node_modules/foo.js:2:7)\n' +
2622+
' at get bar (/foo/node_modules/bar.js:827:30))>]\n' +
2623+
' })>]\n' +
2624+
'}'
2625+
);
2626+
}
2627+
2628+
// Property getter throwing an error with getters that throws recursivly.
2629+
{
2630+
const recursivelyThrowingErrorDesc = {
2631+
__proto__: null,
2632+
// eslint-disable-next-line no-restricted-syntax
2633+
get() { throw createRecursivelyThrowingError(); },
2634+
configurable: true,
2635+
enumerable: true,
2636+
};
2637+
const createRecursivelyThrowingError = () =>
2638+
Object.defineProperties(new Error(), {
2639+
cause: recursivelyThrowingErrorDesc,
2640+
name: recursivelyThrowingErrorDesc,
2641+
message: recursivelyThrowingErrorDesc,
2642+
stack: recursivelyThrowingErrorDesc,
2643+
});
2644+
const thrower = Object.defineProperty({}, 'foo', recursivelyThrowingErrorDesc);
2645+
2646+
assert.strictEqual(
2647+
inspect(thrower, { getters: true, depth: 1 }),
2648+
'{\n' +
2649+
' foo: [Getter: <Inspection threw ([object Error] {\n' +
2650+
' stack: [Getter/Setter: <Inspection threw ([Error])>],\n' +
2651+
' cause: [Getter: <Inspection threw ([Error])>],\n' +
2652+
' name: [Getter: <Inspection threw ([Error])>],\n' +
2653+
' message: [Getter: <Inspection threw ([Error])>]\n' +
2654+
' })>]\n' +
2655+
'}'
2656+
);
2657+
2658+
[{ getters: true, depth: 2 }, { getters: true }].forEach((options) => {
2659+
assert.strictEqual(
2660+
inspect(thrower, options),
2661+
'{\n' +
2662+
' foo: [Getter: <Inspection threw ([object Error] {\n' +
2663+
' stack: [Getter/Setter: <Inspection threw ([object Error] {\n' +
2664+
' stack: [Getter/Setter: <Inspection threw ([Error])>],\n' +
2665+
' cause: [Getter: <Inspection threw ([Error])>],\n' +
2666+
' name: [Getter: <Inspection threw ([Error])>],\n' +
2667+
' message: [Getter: <Inspection threw ([Error])>]\n' +
2668+
' })>],\n' +
2669+
' cause: [Getter: <Inspection threw ([object Error] {\n' +
2670+
' stack: [Getter/Setter: <Inspection threw ([Error])>],\n' +
2671+
' cause: [Getter: <Inspection threw ([Error])>],\n' +
2672+
' name: [Getter: <Inspection threw ([Error])>],\n' +
2673+
' message: [Getter: <Inspection threw ([Error])>]\n' +
2674+
' })>],\n' +
2675+
' name: [Getter: <Inspection threw ([object Error] {\n' +
2676+
' stack: [Getter/Setter: <Inspection threw ([Error])>],\n' +
2677+
' cause: [Getter: <Inspection threw ([Error])>],\n' +
2678+
' name: [Getter: <Inspection threw ([Error])>],\n' +
2679+
' message: [Getter: <Inspection threw ([Error])>]\n' +
2680+
' })>],\n' +
2681+
' message: [Getter: <Inspection threw ([object Error] {\n' +
2682+
' stack: [Getter/Setter: <Inspection threw ([Error])>],\n' +
2683+
' cause: [Getter: <Inspection threw ([Error])>],\n' +
2684+
' name: [Getter: <Inspection threw ([Error])>],\n' +
2685+
' message: [Getter: <Inspection threw ([Error])>]\n' +
2686+
' })>]\n' +
2687+
' })>]\n' +
2688+
'}'
2689+
);
2690+
});
2691+
}
2692+
2693+
// Property getter throwing an error whose own getters throw that same error (infinite recursion).
2694+
{
2695+
const badError = new Error();
2696+
2697+
const throwingGetter = {
2698+
__proto__: null,
2699+
get() {
2700+
throw badError;
2701+
},
2702+
configurable: true,
2703+
enumerable: true,
2704+
};
2705+
2706+
Object.defineProperties(badError, {
2707+
name: throwingGetter,
2708+
message: throwingGetter,
2709+
stack: throwingGetter,
2710+
cause: throwingGetter,
2711+
});
2712+
2713+
const thrower = {
2714+
get foo() { throw badError; }
2715+
};
2716+
2717+
assert.strictEqual(
2718+
inspect(thrower, { getters: true, depth: Infinity }),
2719+
'{\n' +
2720+
' foo: [Getter: <Inspection threw (<ref *1> [object Error] {\n' +
2721+
' stack: [Getter/Setter: <Inspection threw ([Circular *1])>],\n' +
2722+
' name: [Getter: <Inspection threw ([Circular *1])>],\n' +
2723+
' message: [Getter: <Inspection threw ([Circular *1])>],\n' +
2724+
' cause: [Getter: <Inspection threw ([Circular *1])>]\n' +
2725+
' })>]\n' +
2726+
'}'
2727+
);
2728+
}
2729+
2730+
// Property getter throwing uncommon values.
2731+
[
2732+
{
2733+
val: undefined,
2734+
expected: '{ foo: [Getter: <Inspection threw (undefined)>] }'
2735+
},
2736+
{
2737+
val: null,
2738+
expected: '{ foo: [Getter: <Inspection threw (null)>] }'
2739+
},
2740+
{
2741+
val: true,
2742+
expected: '{ foo: [Getter: <Inspection threw (true)>] }'
2743+
},
2744+
{
2745+
val: 1,
2746+
expected: '{ foo: [Getter: <Inspection threw (1)>] }'
2747+
},
2748+
{
2749+
val: 1n,
2750+
expected: '{ foo: [Getter: <Inspection threw (1n)>] }'
2751+
},
2752+
{
2753+
val: Symbol(),
2754+
expected: '{ foo: [Getter: <Inspection threw (Symbol())>] }'
2755+
},
2756+
{
2757+
val: () => {},
2758+
expected: '{ foo: [Getter: <Inspection threw ([Function: val])>] }'
2759+
},
2760+
{
2761+
val: 'string',
2762+
expected: "{ foo: [Getter: <Inspection threw ('string')>] }"
2763+
},
2764+
{
2765+
val: [],
2766+
expected: '{ foo: [Getter: <Inspection threw ([])>] }'
2767+
},
2768+
{
2769+
val: { get message() { return 'Oops'; } },
2770+
expected: "{ foo: [Getter: <Inspection threw ({ message: [Getter: 'Oops'] })>] }"
2771+
},
2772+
{
2773+
val: Error,
2774+
expected: '{ foo: [Getter: <Inspection threw ([Function: Error])>] }'
2775+
},
2776+
].forEach(({ val, expected }) => {
2777+
assert.strictEqual(
2778+
inspect({
2779+
get foo() { throw val; }
2780+
}, { getters: true }),
2781+
expected,
2782+
);
2783+
});
2784+
25542785
// Check compact number mode.
25552786
{
25562787
let obj = {
@@ -3231,25 +3462,26 @@ assert.strictEqual(
32313462
'\x1B[2mdef: \x1B[33m5\x1B[39m\x1B[22m }'
32323463
);
32333464

3234-
assert.strictEqual(
3465+
assert.match(
32353466
inspect(Object.getPrototypeOf(bar), { showHidden: true, getters: true }),
3236-
'<ref *1> Foo [Map] {\n' +
3237-
' [constructor]: [class Bar extends Foo] {\n' +
3238-
' [length]: 0,\n' +
3239-
" [name]: 'Bar',\n" +
3240-
' [prototype]: [Circular *1],\n' +
3241-
' [Symbol(Symbol.species)]: [Getter: <Inspection threw ' +
3242-
"(Symbol.prototype.toString requires that 'this' be a Symbol)>]\n" +
3243-
' },\n' +
3244-
" [xyz]: [Getter: 'YES!'],\n" +
3245-
' [Symbol(nodejs.util.inspect.custom)]: ' +
3246-
'[Function: [nodejs.util.inspect.custom]] {\n' +
3247-
' [length]: 0,\n' +
3248-
" [name]: '[nodejs.util.inspect.custom]'\n" +
3249-
' },\n' +
3250-
' [abc]: [Getter: true],\n' +
3251-
' [def]: [Getter/Setter: false]\n' +
3252-
' }'
3467+
new RegExp('^' + RegExp.escape(
3468+
'<ref *1> Foo [Map] {\n' +
3469+
' [constructor]: [class Bar extends Foo] {\n' +
3470+
' [length]: 0,\n' +
3471+
" [name]: 'Bar',\n" +
3472+
' [prototype]: [Circular *1],\n' +
3473+
' [Symbol(Symbol.species)]: [Getter: <Inspection threw ' +
3474+
"(TypeError: Symbol.prototype.toString requires that 'this' be a Symbol") + '.*' + RegExp.escape(')>]\n' +
3475+
' },\n' +
3476+
" [xyz]: [Getter: 'YES!'],\n" +
3477+
' [Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]] {\n' +
3478+
' [length]: 0,\n' +
3479+
" [name]: '[nodejs.util.inspect.custom]'\n" +
3480+
' },\n' +
3481+
' [abc]: [Getter: true],\n' +
3482+
' [def]: [Getter/Setter: false]\n' +
3483+
'}'
3484+
) + '$', 's')
32533485
);
32543486

32553487
assert.strictEqual(

0 commit comments

Comments
 (0)