Skip to content

Commit a32cbe1

Browse files
committed
util: fix proxy inspection
This prevents any proxy traps from being called while inspecting proxy objects. That guarantees a side-effect free way of inspecting proxies. PR-URL: #26241 Fixes: #10731 Fixes: #26231 Refs: #25212 Refs: #24765 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 9edce1e commit a32cbe1

File tree

2 files changed

+62
-22
lines changed

2 files changed

+62
-22
lines changed

lib/internal/util/inspect.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -495,18 +495,23 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
495495
return ctx.stylize('null', 'null');
496496
}
497497

498+
// Memorize the context for custom inspection on proxies.
499+
const context = value;
500+
// Always check for proxies to prevent side effects and to prevent triggering
501+
// any proxy handlers.
502+
const proxy = getProxyDetails(value);
503+
if (proxy !== undefined) {
504+
if (ctx.showProxy && ctx.stop === undefined) {
505+
return formatProxy(ctx, proxy, recurseTimes);
506+
}
507+
value = proxy[0];
508+
}
509+
498510
if (ctx.stop !== undefined) {
499511
const name = getConstructorName(value, ctx) || value[Symbol.toStringTag];
500512
return ctx.stylize(`[${name || 'Object'}]`, 'special');
501513
}
502514

503-
if (ctx.showProxy) {
504-
const proxy = getProxyDetails(value);
505-
if (proxy !== undefined) {
506-
return formatProxy(ctx, proxy, recurseTimes);
507-
}
508-
}
509-
510515
// Provide a hook for user-specified inspect functions.
511516
// Check that value is an object with an inspect function on it.
512517
if (ctx.customInspect) {
@@ -523,11 +528,10 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
523528
// This makes sure the recurseTimes are reported as before while using
524529
// a counter internally.
525530
const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
526-
const ret = maybeCustom.call(value, depth, plainCtx);
527-
531+
const ret = maybeCustom.call(context, depth, plainCtx);
528532
// If the custom inspection method returned `this`, don't go into
529533
// infinite recursion.
530-
if (ret !== value) {
534+
if (ret !== context) {
531535
if (typeof ret !== 'string') {
532536
return formatValue(ctx, ret, recurseTimes);
533537
}

test/parallel/test-util-inspect-proxy.js

+48-12
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,35 @@ const { internalBinding } = require('internal/test/binding');
88
const processUtil = internalBinding('util');
99
const opts = { showProxy: true };
1010

11-
const target = {};
11+
let proxyObj;
12+
let called = false;
13+
const target = {
14+
[util.inspect.custom](depth, { showProxy }) {
15+
if (showProxy === false) {
16+
called = true;
17+
if (proxyObj !== this) {
18+
throw new Error('Failed');
19+
}
20+
}
21+
return [1, 2, 3];
22+
}
23+
};
1224
const handler = {
13-
get: function() { throw new Error('Getter should not be called'); }
25+
getPrototypeOf() { throw new Error('getPrototypeOf'); },
26+
setPrototypeOf() { throw new Error('setPrototypeOf'); },
27+
isExtensible() { throw new Error('isExtensible'); },
28+
preventExtensions() { throw new Error('preventExtensions'); },
29+
getOwnPropertyDescriptor() { throw new Error('getOwnPropertyDescriptor'); },
30+
defineProperty() { throw new Error('defineProperty'); },
31+
has() { throw new Error('has'); },
32+
get() { throw new Error('get'); },
33+
set() { throw new Error('set'); },
34+
deleteProperty() { throw new Error('deleteProperty'); },
35+
ownKeys() { throw new Error('ownKeys'); },
36+
apply() { throw new Error('apply'); },
37+
construct() { throw new Error('construct'); }
1438
};
15-
const proxyObj = new Proxy(target, handler);
39+
proxyObj = new Proxy(target, handler);
1640

1741
// Inspecting the proxy should not actually walk it's properties
1842
util.inspect(proxyObj, opts);
@@ -23,19 +47,31 @@ const details = processUtil.getProxyDetails(proxyObj);
2347
assert.strictEqual(target, details[0]);
2448
assert.strictEqual(handler, details[1]);
2549

26-
assert.strictEqual(util.inspect(proxyObj, opts),
27-
'Proxy [ {}, { get: [Function: get] } ]');
50+
assert.strictEqual(
51+
util.inspect(proxyObj, opts),
52+
'Proxy [ [ 1, 2, 3 ],\n' +
53+
' { getPrototypeOf: [Function: getPrototypeOf],\n' +
54+
' setPrototypeOf: [Function: setPrototypeOf],\n' +
55+
' isExtensible: [Function: isExtensible],\n' +
56+
' preventExtensions: [Function: preventExtensions],\n' +
57+
' getOwnPropertyDescriptor: [Function: getOwnPropertyDescriptor],\n' +
58+
' defineProperty: [Function: defineProperty],\n' +
59+
' has: [Function: has],\n' +
60+
' get: [Function: get],\n' +
61+
' set: [Function: set],\n' +
62+
' deleteProperty: [Function: deleteProperty],\n' +
63+
' ownKeys: [Function: ownKeys],\n' +
64+
' apply: [Function: apply],\n' +
65+
' construct: [Function: construct] } ]'
66+
);
2867

2968
// Using getProxyDetails with non-proxy returns undefined
3069
assert.strictEqual(processUtil.getProxyDetails({}), undefined);
3170

32-
// This will throw because the showProxy option is not used
33-
// and the get function on the handler object defined above
34-
// is actually invoked.
35-
assert.throws(
36-
() => util.inspect(proxyObj),
37-
/^Error: Getter should not be called$/
38-
);
71+
// Inspecting a proxy without the showProxy option set to true should not
72+
// trigger any proxy handlers.
73+
assert.strictEqual(util.inspect(proxyObj), '[ 1, 2, 3 ]');
74+
assert(called);
3975

4076
// Yo dawg, I heard you liked Proxy so I put a Proxy
4177
// inside your Proxy that proxies your Proxy's Proxy.

0 commit comments

Comments
 (0)