util: correctly inspect Map/Set Iterators
authorEvan Lucas <evanlucas@me.com>
Tue, 29 Sep 2015 19:30:22 +0000 (14:30 -0500)
committerJames M Snell <jasnell@gmail.com>
Thu, 8 Oct 2015 03:39:15 +0000 (20:39 -0700)
Previously, a MapIterator or SetIterator would
not be inspected properly. This change makes it possible
to inspect them by creating a Debug Mirror and previewing
the iterators to not consume the actual iterator that
we are trying to inspect.

This change also adds a node_util binding that uses
v8's Value::IsSetIterator and Value::IsMapIterator
to verify that the values passed in are actual iterators.

Fixes: https://github.com/nodejs/node/issues/3107
PR-URL: https://github.com/nodejs/node/pull/3119
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
lib/util.js
node.gyp
src/node_util.cc [new file with mode: 0644]
test/parallel/test-util-inspect.js

index 401a0ed..41c5b44 100644 (file)
@@ -3,6 +3,7 @@
 const uv = process.binding('uv');
 const Buffer = require('buffer').Buffer;
 const internalUtil = require('internal/util');
+const binding = process.binding('util');
 
 var Debug;
 var ObjectIsPromise;
@@ -323,11 +324,23 @@ function formatValue(ctx, value, recurseTimes) {
       braces = ['{', '}'];
       formatter = formatPromise;
     } else {
-      if (constructor === Object)
-        constructor = null;
-      braces = ['{', '}'];
-      empty = true;  // No other data than keys.
-      formatter = formatObject;
+      if (binding.isMapIterator(value)) {
+        constructor = { name: 'MapIterator' };
+        braces = ['{', '}'];
+        empty = false;
+        formatter = formatCollectionIterator;
+      } else if (binding.isSetIterator(value)) {
+        constructor = { name: 'SetIterator' };
+        braces = ['{', '}'];
+        empty = false;
+        formatter = formatCollectionIterator;
+      } else {
+        if (constructor === Object)
+          constructor = null;
+        braces = ['{', '}'];
+        empty = true;  // No other data than keys.
+        formatter = formatObject;
+      }
     }
   }
 
@@ -501,6 +514,18 @@ function formatMap(ctx, value, recurseTimes, visibleKeys, keys) {
   return output;
 }
 
+function formatCollectionIterator(ctx, value, recurseTimes, visibleKeys, keys) {
+  ensureDebugIsInitialized();
+  const mirror = Debug.MakeMirror(value, true);
+  var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
+  var vals = mirror.preview();
+  var output = [];
+  for (let o of vals) {
+    output.push(formatValue(ctx, o, nextRecurseTimes));
+  }
+  return output;
+}
+
 function formatPromise(ctx, value, recurseTimes, visibleKeys, keys) {
   var output = [];
   var internals = inspectPromise(value);
index 9575f4c..ece9eee 100644 (file)
--- a/node.gyp
+++ b/node.gyp
         'src/node_javascript.cc',
         'src/node_main.cc',
         'src/node_os.cc',
+        'src/node_util.cc',
         'src/node_v8.cc',
         'src/node_stat_watcher.cc',
         'src/node_watchdog.cc',
diff --git a/src/node_util.cc b/src/node_util.cc
new file mode 100644 (file)
index 0000000..cc86478
--- /dev/null
@@ -0,0 +1,38 @@
+#include "node.h"
+#include "v8.h"
+#include "env.h"
+#include "env-inl.h"
+
+namespace node {
+namespace util {
+
+using v8::Context;
+using v8::FunctionCallbackInfo;
+using v8::Local;
+using v8::Object;
+using v8::Value;
+
+static void IsMapIterator(const FunctionCallbackInfo<Value>& args) {
+  CHECK_EQ(1, args.Length());
+  args.GetReturnValue().Set(args[0]->IsMapIterator());
+}
+
+
+static void IsSetIterator(const FunctionCallbackInfo<Value>& args) {
+  CHECK_EQ(1, args.Length());
+  args.GetReturnValue().Set(args[0]->IsSetIterator());
+}
+
+
+void Initialize(Local<Object> target,
+                Local<Value> unused,
+                Local<Context> context) {
+  Environment* env = Environment::GetCurrent(context);
+  env->SetMethod(target, "isMapIterator", IsMapIterator);
+  env->SetMethod(target, "isSetIterator", IsSetIterator);
+}
+
+}  // namespace util
+}  // namespace node
+
+NODE_MODULE_CONTEXT_AWARE_BUILTIN(util, node::util::Initialize)
index aa6c764..a706f49 100644 (file)
@@ -287,6 +287,26 @@ global.Promise = function() { this.bar = 42; };
 assert.equal(util.inspect(new Promise()), '{ bar: 42 }');
 global.Promise = oldPromise;
 
+// Map/Set Iterators
+var m = new Map([['foo', 'bar']]);
+assert.strictEqual(util.inspect(m.keys()), 'MapIterator { \'foo\' }');
+assert.strictEqual(util.inspect(m.values()), 'MapIterator { \'bar\' }');
+assert.strictEqual(util.inspect(m.entries()),
+                   'MapIterator { [ \'foo\', \'bar\' ] }');
+// make sure the iterator doesn't get consumed
+var keys = m.keys();
+assert.strictEqual(util.inspect(keys), 'MapIterator { \'foo\' }');
+assert.strictEqual(util.inspect(keys), 'MapIterator { \'foo\' }');
+
+var s = new Set([1, 3]);
+assert.strictEqual(util.inspect(s.keys()), 'SetIterator { 1, 3 }');
+assert.strictEqual(util.inspect(s.values()), 'SetIterator { 1, 3 }');
+assert.strictEqual(util.inspect(s.entries()),
+                   'SetIterator { [ 1, 1 ], [ 3, 3 ] }');
+// make sure the iterator doesn't get consumed
+keys = s.keys();
+assert.strictEqual(util.inspect(keys), 'SetIterator { 1, 3 }');
+assert.strictEqual(util.inspect(keys), 'SetIterator { 1, 3 }');
 
 // Test alignment of items in container
 // Assumes that the first numeric character is the start of an item.