Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

util: never trigger proxy traps while inspecting objects #30767

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions benchmark/util/format.js
Expand Up @@ -13,6 +13,8 @@ const inputs = {
'no-replace-2': ['foobar', 'yeah', 'mensch', 5],
'only-objects': [{ msg: 'This is an error' }, { msg: 'This is an error' }],
'many-%': ['replace%%%%s%%%%many%s%s%s', 'percent'],
'object-to-string': ['foo %s bar', { toString() { return 'bla'; } }],
'object-%s': ['foo %s bar', { a: true, b: false }],
};

const bench = common.createBenchmark(main, {
Expand Down
18 changes: 13 additions & 5 deletions benchmark/util/inspect-proxy.js
Expand Up @@ -3,13 +3,21 @@
const util = require('util');
const common = require('../common.js');

const bench = common.createBenchmark(main, { n: [2e4] });
const bench = common.createBenchmark(main, {
n: [2e4],
showProxy: [0, 1],
isProxy: [0, 1]
});

function main({ n }) {
const proxyA = new Proxy({}, { get: () => {} });
const proxyB = new Proxy(() => {}, {});
function main({ n, showProxy, isProxy }) {
let proxyA = {};
let proxyB = () => {};
if (isProxy) {
proxyA = new Proxy(proxyA, { get: () => {} });
proxyB = new Proxy(proxyB, {});
}
bench.start();
for (let i = 0; i < n; i += 1)
util.inspect({ a: proxyA, b: proxyB }, { showProxy: true });
util.inspect({ a: proxyA, b: proxyB }, { showProxy });
bench.end(n);
}
11 changes: 9 additions & 2 deletions lib/internal/util/inspect.js
Expand Up @@ -570,12 +570,12 @@ 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);
const proxy = getProxyDetails(value, ctx.showProxy);
if (proxy !== undefined) {
if (ctx.showProxy) {
return formatProxy(ctx, proxy, recurseTimes);
}
value = proxy[0];
value = proxy;
}

// Provide a hook for user-specified inspect functions.
Expand Down Expand Up @@ -1575,6 +1575,13 @@ function reduceToSingleString(
}

function hasBuiltInToString(value) {
// Prevent triggering proxy traps.
const getFullProxy = false;
const proxyTarget = getProxyDetails(value, getFullProxy);
if (proxyTarget !== undefined) {
value = proxyTarget;
}

// Count objects that have no `toString` function as built-in.
if (typeof value.toString !== 'function') {
return true;
Expand Down
20 changes: 13 additions & 7 deletions src/node_util.cc
Expand Up @@ -93,13 +93,19 @@ static void GetProxyDetails(const FunctionCallbackInfo<Value>& args) {

Local<Proxy> proxy = args[0].As<Proxy>();

Local<Value> ret[] = {
proxy->GetTarget(),
proxy->GetHandler()
};

args.GetReturnValue().Set(
Array::New(args.GetIsolate(), ret, arraysize(ret)));
if (args[1]->IsTrue()) {
Local<Value> ret[] = {
proxy->GetTarget(),
proxy->GetHandler()
};

args.GetReturnValue().Set(
Array::New(args.GetIsolate(), ret, arraysize(ret)));
} else {
Local<Value> ret = proxy->GetTarget();

args.GetReturnValue().Set(ret);
}
}

static void PreviewEntries(const FunctionCallbackInfo<Value>& args) {
Expand Down
4 changes: 3 additions & 1 deletion test/benchmark/test-benchmark-util.js
Expand Up @@ -14,5 +14,7 @@ runBenchmark('util',
'size=1',
'type=',
'len=1',
'version=native'],
'version=native',
'isProxy=1',
'showProxy=1'],
{ NODEJS_BENCHMARK_ZERO_ALLOWED: 1 });
8 changes: 7 additions & 1 deletion test/parallel/test-util-inspect-proxy.js
Expand Up @@ -41,12 +41,18 @@ proxyObj = new Proxy(target, handler);
// Inspecting the proxy should not actually walk it's properties
util.inspect(proxyObj, opts);

// Make sure inspecting object does not trigger any proxy traps.
util.format('%s', proxyObj);

// getProxyDetails is an internal method, not intended for public use.
// This is here to test that the internals are working correctly.
const details = processUtil.getProxyDetails(proxyObj);
let details = processUtil.getProxyDetails(proxyObj, true);
assert.strictEqual(target, details[0]);
assert.strictEqual(handler, details[1]);

details = processUtil.getProxyDetails(proxyObj, false);
assert.strictEqual(target, details);

assert.strictEqual(
util.inspect(proxyObj, opts),
'Proxy [\n' +
Expand Down