From 86daa71190e68f44b64c92f2bc38b101a8de0f7f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 29 Apr 2016 01:06:48 -0700 Subject: [PATCH] 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: https://github.com/nodejs/node/issues/6464 --- doc/api/util.md | 16 +++++++++++++ lib/util.js | 11 +++++++++ src/env.h | 2 ++ src/node_util.cc | 18 +++++++++++++++ test/parallel/test-util-inspect-proxy.js | 29 ++++++++++++++++++++++++ 5 files changed, 76 insertions(+) create mode 100644 test/parallel/test-util-inspect-proxy.js diff --git a/doc/api/util.md b/doc/api/util.md index d298ae4b44425a..67caab98368df6 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -478,6 +478,22 @@ util.isPrimitive(new Date()) // false ``` +## util.isProxy(object) + +Returns `true` if the given "object" is a `Proxy` object. Otherwise, returns +`false`. + +```js +const util = require('util'); +const proxyObj = new Proxy({}, {get: () => { return 5; }}); + +util.isProxy(proxyObj); + // true + +util.isProxy({}); + // false +``` + ## util.isRegExp(object) Stability: 0 - Deprecated diff --git a/lib/util.js b/lib/util.js index 63d6d0f3c865af..b8d4c8770b5212 100644 --- a/lib/util.js +++ b/lib/util.js @@ -241,6 +241,13 @@ function inspectPromise(p) { function formatValue(ctx, value, recurseTimes) { + + if (isProxy(value)) { + return 'Proxy ' + formatValue(ctx, + binding.getProxyDetails(value), + recurseTimes); + } + // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it if (ctx.customInspect && @@ -785,6 +792,10 @@ exports.isPrimitive = isPrimitive; exports.isBuffer = Buffer.isBuffer; +function isProxy(p) { + return binding.isProxy(p); +} +exports.isProxy = isProxy; function pad(n) { return n < 10 ? '0' + n.toString(10) : n.toString(10); diff --git a/src/env.h b/src/env.h index afbade5dd81e70..fe0c636549eb6d 100644 --- a/src/env.h +++ b/src/env.h @@ -121,6 +121,7 @@ namespace node { V(fsevent_string, "FSEvent") \ V(gid_string, "gid") \ V(handle_string, "handle") \ + V(handler_string, "handler") \ V(heap_total_string, "heapTotal") \ V(heap_used_string, "heapUsed") \ V(homedir_string, "homedir") \ @@ -222,6 +223,7 @@ namespace node { V(subjectaltname_string, "subjectaltname") \ V(sys_string, "sys") \ V(syscall_string, "syscall") \ + V(target_string, "target") \ V(tick_callback_string, "_tickCallback") \ V(tick_domain_cb_string, "_tickDomainCallback") \ V(ticketkeycallback_string, "onticketkeycallback") \ diff --git a/src/node_util.cc b/src/node_util.cc index 1c5a2fa9a1f373..490b4a4f74c366 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -11,6 +11,7 @@ using v8::FunctionCallbackInfo; using v8::Local; using v8::Object; using v8::Private; +using v8::Proxy; using v8::String; using v8::Value; @@ -22,6 +23,7 @@ using v8::Value; V(isMap, IsMap) \ V(isMapIterator, IsMapIterator) \ V(isPromise, IsPromise) \ + V(isProxy, IsProxy) \ V(isRegExp, IsRegExp) \ V(isSet, IsSet) \ V(isSetIterator, IsSetIterator) \ @@ -37,6 +39,21 @@ using v8::Value; VALUE_METHOD_MAP(V) #undef V +static void GetProxyDetails(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + // Return nothing if it's not a proxy. + if (!args[0]->IsProxy()) + return; + + Local proxy = args[0].As(); + + Local ret = Object::New(env->isolate()); + ret->Set(env->handler_string(), proxy->GetHandler()); + ret->Set(env->target_string(), proxy->GetTarget()); + + args.GetReturnValue().Set(ret); +} static void GetHiddenValue(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -84,6 +101,7 @@ void Initialize(Local target, env->SetMethod(target, "getHiddenValue", GetHiddenValue); env->SetMethod(target, "setHiddenValue", SetHiddenValue); + env->SetMethod(target, "getProxyDetails", GetProxyDetails); } } // namespace util diff --git a/test/parallel/test-util-inspect-proxy.js b/test/parallel/test-util-inspect-proxy.js new file mode 100644 index 00000000000000..6448df90c2a60f --- /dev/null +++ b/test/parallel/test-util-inspect-proxy.js @@ -0,0 +1,29 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const util = require('util'); +const processUtil = process.binding('util'); + +const target = {}; +const handler = { + get: function() { throw new Error('Getter should not be called'); } +}; +const proxyObj = new Proxy(target, handler); + +assert.strictEqual(util.isProxy(proxyObj), true); + +// Inspecting the proxy should not actually walk it's properties +assert.doesNotThrow(() => util.inspect(proxyObj)); + +const details = processUtil.getProxyDetails(proxyObj); +assert.deepStrictEqual(target, details.target); +assert.deepStrictEqual(handler, details.handler); + +assert.strictEqual(util.inspect(proxyObj), + 'Proxy { handler: { get: [Function] }, target: {} }'); + +// Using getProxyDetails with non-proxy returns undefined +assert.strictEqual(processUtil.getProxyDetails({}), undefined); +// isProxy with non-Proxy returns false +assert.strictEqual(util.isProxy({}), false);