Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -1118,17 +1118,29 @@ function formatValue(ctx, value, recurseTimes, typedArray) {

// Memorize the context for custom inspection on proxies.
const context = value;
let proxies = 0;
// Always check for proxies to prevent side effects and to prevent triggering
// any proxy handlers.
const proxy = getProxyDetails(value, !!ctx.showProxy);
let proxy = getProxyDetails(value, !!ctx.showProxy);
if (proxy !== undefined) {
if (proxy === null || proxy[0] === null) {
return ctx.stylize('<Revoked Proxy>', 'special');
}
if (ctx.showProxy) {
if (proxy[0] === null) {
return ctx.stylize('<Revoked Proxy>', 'special');
}
return formatProxy(ctx, proxy, recurseTimes);
}
value = proxy;
do {
if (proxy === null) {
let formatted = ctx.stylize('<Revoked Proxy>', 'special');
for (let i = 0; i < proxies; i++) {
formatted = `${ctx.stylize('Proxy(', 'special')}${formatted}${ctx.stylize(')', 'special')}`;
}
return formatted;
}
value = proxy;
proxy = getProxyDetails(value, false);
proxies += 1;
} while (proxy !== undefined);
}

// Provide a hook for user-specified inspect functions.
Expand All @@ -1144,7 +1156,7 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
// a counter internally.
const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
const isCrossContext =
proxy !== undefined || !FunctionPrototypeSymbolHasInstance(Object, context);
proxies !== 0 || !FunctionPrototypeSymbolHasInstance(Object, context);
const ret = FunctionPrototypeCall(
maybeCustom,
context,
Expand Down Expand Up @@ -1180,10 +1192,12 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
return ctx.stylize(`[Circular *${index}]`, 'special');
}

const formatted = formatRaw(ctx, value, recurseTimes, typedArray);
let formatted = formatRaw(ctx, value, recurseTimes, typedArray);

if (proxy !== undefined) {
return `${ctx.stylize('Proxy(', 'special')}${formatted}${ctx.stylize(')', 'special')}`;
if (proxies !== 0) {
for (let i = 0; i < proxies; i++) {
formatted = `${ctx.stylize('Proxy(', 'special')}${formatted}${ctx.stylize(')', 'special')}`;
}
}

return formatted;
Expand Down Expand Up @@ -2691,7 +2705,7 @@ function hasBuiltInToString(value) {
if (proxyTarget === null) {
return true;
}
value = proxyTarget;
return hasBuiltInToString(proxyTarget);
}

let hasOwnToString = ObjectPrototypeHasOwnProperty;
Expand Down
41 changes: 35 additions & 6 deletions test/parallel/test-util-inspect-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ proxyObj = new Proxy(target, handler);
util.inspect(proxyObj, opts);

// Make sure inspecting object does not trigger any proxy traps.
util.format('%s', proxyObj);
// %i%f%d use Symbol.toPrimitive to convert the value to a string.
// %j uses JSON.stringify, accessing the value's toJSON and toString method.
util.format('%s%o%O%c', proxyObj, proxyObj, proxyObj, proxyObj);
const nestedProxy = new Proxy(new Proxy({}, handler), {});
util.format('%s%o%O%c', nestedProxy, nestedProxy, nestedProxy, nestedProxy);

// getProxyDetails is an internal method, not intended for public use.
// This is here to test that the internals are working correctly.
Expand Down Expand Up @@ -135,6 +139,10 @@ const expected6 = 'Proxy [\n' +
' Proxy [ Proxy [Array], Proxy [Array] ]\n' +
' ]\n' +
']';
const expected2NoShowProxy = 'Proxy(Proxy({}))';
const expected3NoShowProxy = 'Proxy(Proxy(Proxy({})))';
const expected4NoShowProxy = 'Proxy(Proxy(Proxy(Proxy({}))))';
const expected5NoShowProxy = 'Proxy(Proxy(Proxy(Proxy(Proxy({})))))';
assert.strictEqual(
util.inspect(proxy1, { showProxy: 1, depth: null }),
expected1);
Expand All @@ -144,11 +152,11 @@ assert.strictEqual(util.inspect(proxy4, opts), expected4);
assert.strictEqual(util.inspect(proxy5, opts), expected5);
assert.strictEqual(util.inspect(proxy6, opts), expected6);
assert.strictEqual(util.inspect(proxy1), expected0);
assert.strictEqual(util.inspect(proxy2), expected0);
assert.strictEqual(util.inspect(proxy3), expected0);
assert.strictEqual(util.inspect(proxy4), expected0);
assert.strictEqual(util.inspect(proxy5), expected0);
assert.strictEqual(util.inspect(proxy6), expected0);
assert.strictEqual(util.inspect(proxy2), expected2NoShowProxy);
assert.strictEqual(util.inspect(proxy3), expected3NoShowProxy);
assert.strictEqual(util.inspect(proxy4), expected2NoShowProxy);
assert.strictEqual(util.inspect(proxy5), expected4NoShowProxy);
assert.strictEqual(util.inspect(proxy6), expected5NoShowProxy);

// Just for fun, let's create a Proxy using Arrays.
const proxy7 = new Proxy([], []);
Expand Down Expand Up @@ -188,3 +196,24 @@ assert.strictEqual(
')\x1B[39m'
);
assert.strictEqual(util.format('%s', proxy12), 'Proxy([ 1, 2, 3 ])');

{
// Nested proxies should not trigger any proxy handlers.
const nestedProxy = new Proxy(new Proxy(new Proxy({}, handler), {}), {});

assert.strictEqual(
util.inspect(nestedProxy, { showProxy: true }),
'Proxy [ Proxy [ Proxy [ {}, [Object] ], {} ], {} ]'
);
assert.strictEqual(util.inspect(nestedProxy, { showProxy: false }), expected3NoShowProxy);
}

{
// Nested revoked proxies should work as expected as well as custom inspection functions.
const revocable = Proxy.revocable({}, handler);
revocable.revoke();
const nestedProxy = new Proxy(revocable.proxy, {});

assert.strictEqual(util.inspect(nestedProxy, { showProxy: true }), 'Proxy [ <Revoked Proxy>, {} ]');
assert.strictEqual(util.inspect(nestedProxy, { showProxy: false }), 'Proxy(<Revoked Proxy>)');
}
Loading