Skip to content

Commit 86daa71

Browse files
committed
util: fix inspecting of proxy objects
In certain conditions, inspecting a Proxy object can lead to a max call stack error. Avoid that by detecting the Proxy object and outputting information about the Proxy object itself. Also adds util.isProxy() Fixes: #6464
1 parent 1264cec commit 86daa71

File tree

5 files changed

+76
-0
lines changed

5 files changed

+76
-0
lines changed

doc/api/util.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,22 @@ util.isPrimitive(new Date())
478478
// false
479479
```
480480

481+
## util.isProxy(object)
482+
483+
Returns `true` if the given "object" is a `Proxy` object. Otherwise, returns
484+
`false`.
485+
486+
```js
487+
const util = require('util');
488+
const proxyObj = new Proxy({}, {get: () => { return 5; }});
489+
490+
util.isProxy(proxyObj);
491+
// true
492+
493+
util.isProxy({});
494+
// false
495+
```
496+
481497
## util.isRegExp(object)
482498

483499
Stability: 0 - Deprecated

lib/util.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,13 @@ function inspectPromise(p) {
241241

242242

243243
function formatValue(ctx, value, recurseTimes) {
244+
245+
if (isProxy(value)) {
246+
return 'Proxy ' + formatValue(ctx,
247+
binding.getProxyDetails(value),
248+
recurseTimes);
249+
}
250+
244251
// Provide a hook for user-specified inspect functions.
245252
// Check that value is an object with an inspect function on it
246253
if (ctx.customInspect &&
@@ -785,6 +792,10 @@ exports.isPrimitive = isPrimitive;
785792

786793
exports.isBuffer = Buffer.isBuffer;
787794

795+
function isProxy(p) {
796+
return binding.isProxy(p);
797+
}
798+
exports.isProxy = isProxy;
788799

789800
function pad(n) {
790801
return n < 10 ? '0' + n.toString(10) : n.toString(10);

src/env.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ namespace node {
121121
V(fsevent_string, "FSEvent") \
122122
V(gid_string, "gid") \
123123
V(handle_string, "handle") \
124+
V(handler_string, "handler") \
124125
V(heap_total_string, "heapTotal") \
125126
V(heap_used_string, "heapUsed") \
126127
V(homedir_string, "homedir") \
@@ -222,6 +223,7 @@ namespace node {
222223
V(subjectaltname_string, "subjectaltname") \
223224
V(sys_string, "sys") \
224225
V(syscall_string, "syscall") \
226+
V(target_string, "target") \
225227
V(tick_callback_string, "_tickCallback") \
226228
V(tick_domain_cb_string, "_tickDomainCallback") \
227229
V(ticketkeycallback_string, "onticketkeycallback") \

src/node_util.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ using v8::FunctionCallbackInfo;
1111
using v8::Local;
1212
using v8::Object;
1313
using v8::Private;
14+
using v8::Proxy;
1415
using v8::String;
1516
using v8::Value;
1617

@@ -22,6 +23,7 @@ using v8::Value;
2223
V(isMap, IsMap) \
2324
V(isMapIterator, IsMapIterator) \
2425
V(isPromise, IsPromise) \
26+
V(isProxy, IsProxy) \
2527
V(isRegExp, IsRegExp) \
2628
V(isSet, IsSet) \
2729
V(isSetIterator, IsSetIterator) \
@@ -37,6 +39,21 @@ using v8::Value;
3739
VALUE_METHOD_MAP(V)
3840
#undef V
3941

42+
static void GetProxyDetails(const FunctionCallbackInfo<Value>& args) {
43+
Environment* env = Environment::GetCurrent(args);
44+
45+
// Return nothing if it's not a proxy.
46+
if (!args[0]->IsProxy())
47+
return;
48+
49+
Local<Proxy> proxy = args[0].As<Proxy>();
50+
51+
Local<Object> ret = Object::New(env->isolate());
52+
ret->Set(env->handler_string(), proxy->GetHandler());
53+
ret->Set(env->target_string(), proxy->GetTarget());
54+
55+
args.GetReturnValue().Set(ret);
56+
}
4057

4158
static void GetHiddenValue(const FunctionCallbackInfo<Value>& args) {
4259
Environment* env = Environment::GetCurrent(args);
@@ -84,6 +101,7 @@ void Initialize(Local<Object> target,
84101

85102
env->SetMethod(target, "getHiddenValue", GetHiddenValue);
86103
env->SetMethod(target, "setHiddenValue", SetHiddenValue);
104+
env->SetMethod(target, "getProxyDetails", GetProxyDetails);
87105
}
88106

89107
} // namespace util
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const util = require('util');
6+
const processUtil = process.binding('util');
7+
8+
const target = {};
9+
const handler = {
10+
get: function() { throw new Error('Getter should not be called'); }
11+
};
12+
const proxyObj = new Proxy(target, handler);
13+
14+
assert.strictEqual(util.isProxy(proxyObj), true);
15+
16+
// Inspecting the proxy should not actually walk it's properties
17+
assert.doesNotThrow(() => util.inspect(proxyObj));
18+
19+
const details = processUtil.getProxyDetails(proxyObj);
20+
assert.deepStrictEqual(target, details.target);
21+
assert.deepStrictEqual(handler, details.handler);
22+
23+
assert.strictEqual(util.inspect(proxyObj),
24+
'Proxy { handler: { get: [Function] }, target: {} }');
25+
26+
// Using getProxyDetails with non-proxy returns undefined
27+
assert.strictEqual(processUtil.getProxyDetails({}), undefined);
28+
// isProxy with non-Proxy returns false
29+
assert.strictEqual(util.isProxy({}), false);

0 commit comments

Comments
 (0)