diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index e7b0063318c8c8..1560469959e7c7 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -1120,15 +1120,21 @@ function formatValue(ctx, value, recurseTimes, typedArray) { const context = value; // 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('', 'special'); - } if (ctx.showProxy) { + if (proxy[0] === null) { + return ctx.stylize('', 'special'); + } return formatProxy(ctx, proxy, recurseTimes); } - value = proxy; + do { + if (proxy === null) { + return ctx.stylize('', 'special'); + } + value = proxy; + proxy = getProxyDetails(value, false); + } while (proxy !== undefined); } // Provide a hook for user-specified inspect functions. @@ -1143,8 +1149,7 @@ function formatValue(ctx, value, recurseTimes, typedArray) { // This makes sure the recurseTimes are reported as before while using // a counter internally. const depth = ctx.depth === null ? null : ctx.depth - recurseTimes; - const isCrossContext = - proxy !== undefined || !FunctionPrototypeSymbolHasInstance(Object, context); + const isCrossContext = context !== value || !FunctionPrototypeSymbolHasInstance(Object, context); const ret = FunctionPrototypeCall( maybeCustom, context, @@ -2685,7 +2690,7 @@ function hasBuiltInToString(value) { if (proxyTarget === null) { return true; } - value = proxyTarget; + return hasBuiltInToString(proxyTarget); } let hasOwnToString = ObjectPrototypeHasOwnProperty; diff --git a/test/parallel/test-util-inspect-proxy.js b/test/parallel/test-util-inspect-proxy.js index 6344adae990860..faa0fe68b05419 100644 --- a/test/parallel/test-util-inspect-proxy.js +++ b/test/parallel/test-util-inspect-proxy.js @@ -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. @@ -179,3 +183,31 @@ const expected10 = '[Function (anonymous)]'; const expected11 = '[Function (anonymous)]'; assert.strictEqual(util.inspect(proxy10), expected10); assert.strictEqual(util.inspect(proxy11), expected11); + +const proxy12 = new Proxy([1, 2, 3], proxy5); +assert.strictEqual( + util.inspect(proxy12, { colors: true, breakLength: 1 }), + '[\n \x1B[33m1\x1B[39m,\n \x1B[33m2\x1B[39m,\n \x1B[33m3\x1B[39m\n]' +); +assert.strictEqual(util.format('%s', proxy12), '[ 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 }), '{}'); +} + +{ + // 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 [ , {} ]'); + assert.strictEqual(util.inspect(nestedProxy, { showProxy: false }), ''); +}