Skip to content
Open
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
33 changes: 26 additions & 7 deletions src/node/internal/internal_inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1125,17 +1125,29 @@ function formatValue(

// 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 = internal.getProxyDetails(value);
let proxy = internal.getProxyDetails(value);
if (proxy !== undefined) {
if (proxy === null || proxy.target === null) {
return ctx.stylize('<Revoked Proxy>', 'special');
}
if (ctx.showProxy) {
return formatProxy(ctx, proxy, recurseTimes);
}
value = proxy.target;
do {
if (proxy === null || proxy.target === 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.target;
proxy = internal.getProxyDetails(value);
proxies += 1;
} while (proxy !== undefined);
}

// Provide a hook for user-specified inspect functions.
Expand Down Expand Up @@ -1169,8 +1181,7 @@ function formatValue(
// 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 || !(context instanceof Object);
const isCrossContext = proxies !== 0 || !(context instanceof Object);
const ret = Function.prototype.call.call(
maybeCustom,
context,
Expand Down Expand Up @@ -1206,7 +1217,15 @@ function formatValue(
return ctx.stylize(`[Circular *${index}]`, 'special');
}

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

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

return formatted;
}

function formatRaw(
Expand Down Expand Up @@ -2653,10 +2672,10 @@ function hasBuiltInToString(value: object): boolean {
// Prevent triggering proxy traps.
const proxyTarget = internal.getProxyDetails(value);
if (proxyTarget !== undefined) {
if (proxyTarget === null) {
if (proxyTarget === null || proxyTarget.target === null) {
return true;
}
value = proxyTarget.target as object;
return hasBuiltInToString(proxyTarget.target as object);
}

// Count objects that have no `toString` function as built-in.
Expand Down
16 changes: 16 additions & 0 deletions src/node/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ export function writer(): ReplType.REPLWriter & { options: InspectOptions } {
throw new ERR_METHOD_NOT_IMPLEMENTED('writer');
}

const writerOptions: InspectOptions = {
showHidden: false,
depth: 2,
colors: true,
customInspect: true,
showProxy: true,
maxArrayLength: 100,
maxStringLength: 10000,
breakLength: 80,
compact: 3,
sorted: false,
getters: false,
numericSeparator: false,
};
Object.assign(writer, { options: writerOptions });

export function start(): ReplType.REPLServer {
throw new ERR_METHOD_NOT_IMPLEMENTED('start');
}
Expand Down
26 changes: 26 additions & 0 deletions src/workerd/api/node/tests/assert-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
} from 'node:assert';

import { mock } from 'node:test';
import util from 'node:util';

import { default as assert } from 'node:assert';

Expand Down Expand Up @@ -242,6 +243,31 @@ export const test_deep_equal_errors = {
},
};

export const test_check_proxies = {
test() {
const arrProxy = new Proxy([1, 2], {});
deepStrictEqual(arrProxy, [1, 2]);
const defaultMsgStartFull = `${start}\n${actExp}`;
const tmp = util.inspect.defaultOptions;
util.inspect.defaultOptions = { showProxy: true };
throws(() => deepStrictEqual(arrProxy, [1, 2, 3]), {
message:
`${defaultMsgStartFull}\n\n` + '+ Proxy([ 1, 2 ])\n' + '- [ 1, 2, 3 ]',
});
util.inspect.defaultOptions = tmp;

const invalidTrap = new Proxy([1, 2, 3], {
ownKeys() {
return [];
},
});
throws(() => deepStrictEqual(invalidTrap, [1, 2, 3]), {
name: 'TypeError',
message: "'ownKeys' on proxy: trap result did not include 'length'",
});
},
};

export const test_partial_deep_strict_equal = {
test(ctrl, env, ctx) {
// Test basic object partial equality
Expand Down
77 changes: 66 additions & 11 deletions src/workerd/api/node/tests/util-nodejs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3713,6 +3713,17 @@ export const utilInspectProxy = {

// 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 nestedFormatProxy = new Proxy(new Proxy({}, handler), {});
util.format(
'%s%o%O%c',
nestedFormatProxy,
nestedFormatProxy,
nestedFormatProxy,
nestedFormatProxy
);

const r = Proxy.revocable({}, {});
r.revoke();
Expand Down Expand Up @@ -3760,7 +3771,7 @@ export const utilInspectProxy = {
const proxy4 = new Proxy(proxy1, proxy2);
const proxy5 = new Proxy(proxy3, proxy4);
const proxy6 = new Proxy(proxy5, proxy5);
const expected0 = '{}';
const expected0 = 'Proxy({})';
const expected1 = 'Proxy [ {}, {} ]';
const expected2 = 'Proxy [ Proxy [ {}, {} ], {} ]';
const expected3 =
Expand All @@ -3783,6 +3794,10 @@ export const utilInspectProxy = {
' 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 @@ -3793,17 +3808,17 @@ export const utilInspectProxy = {
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([], []);
const expected7 = 'Proxy [ [], [] ]';
assert.strictEqual(util.inspect(proxy7, opts), expected7);
assert.strictEqual(util.inspect(proxy7), '[]');
assert.strictEqual(util.inspect(proxy7), 'Proxy([])');

// Now we're just getting silly, right?
const proxy8 = new Proxy(Date, []);
Expand All @@ -3812,8 +3827,8 @@ export const utilInspectProxy = {
const expected9 = 'Proxy [ [Function: Date], [Function: String] ]';
assert.strictEqual(util.inspect(proxy8, opts), expected8);
assert.strictEqual(util.inspect(proxy9, opts), expected9);
assert.strictEqual(util.inspect(proxy8), '[Function: Date]');
assert.strictEqual(util.inspect(proxy9), '[Function: Date]');
assert.strictEqual(util.inspect(proxy8), 'Proxy([Function: Date])');
assert.strictEqual(util.inspect(proxy9), 'Proxy([Function: Date])');

const proxy10 = new Proxy(() => {}, {});
const proxy11 = new Proxy(() => {}, {
Expand All @@ -3824,10 +3839,50 @@ export const utilInspectProxy = {
return proxy11;
},
});
const expected10 = '[Function (anonymous)]';
const expected11 = '[Function (anonymous)]';
const expected10 = 'Proxy([Function (anonymous)])';
const expected11 = 'Proxy([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 }),
'\x1B[36mProxy(\x1B[39m' +
'[\n \x1B[33m1\x1B[39m,\n \x1B[33m2\x1B[39m,\n \x1B[33m3\x1B[39m\n]\x1B[36m' +
')\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>)'
);
}
},
};

Expand Down
Loading