Skip to content

Commit 8dfdee3

Browse files
evanlucasjasnell
authored andcommitted
util: correctly inspect Map/Set Iterators
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: #3107 PR-URL: #3119 Reviewed-By: Ben Noordhuis <[email protected]>
1 parent b5c51fd commit 8dfdee3

File tree

4 files changed

+89
-5
lines changed

4 files changed

+89
-5
lines changed

lib/util.js

+30-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const uv = process.binding('uv');
44
const Buffer = require('buffer').Buffer;
55
const internalUtil = require('internal/util');
6+
const binding = process.binding('util');
67

78
var Debug;
89
var ObjectIsPromise;
@@ -323,11 +324,23 @@ function formatValue(ctx, value, recurseTimes) {
323324
braces = ['{', '}'];
324325
formatter = formatPromise;
325326
} else {
326-
if (constructor === Object)
327-
constructor = null;
328-
braces = ['{', '}'];
329-
empty = true; // No other data than keys.
330-
formatter = formatObject;
327+
if (binding.isMapIterator(value)) {
328+
constructor = { name: 'MapIterator' };
329+
braces = ['{', '}'];
330+
empty = false;
331+
formatter = formatCollectionIterator;
332+
} else if (binding.isSetIterator(value)) {
333+
constructor = { name: 'SetIterator' };
334+
braces = ['{', '}'];
335+
empty = false;
336+
formatter = formatCollectionIterator;
337+
} else {
338+
if (constructor === Object)
339+
constructor = null;
340+
braces = ['{', '}'];
341+
empty = true; // No other data than keys.
342+
formatter = formatObject;
343+
}
331344
}
332345
}
333346

@@ -501,6 +514,18 @@ function formatMap(ctx, value, recurseTimes, visibleKeys, keys) {
501514
return output;
502515
}
503516

517+
function formatCollectionIterator(ctx, value, recurseTimes, visibleKeys, keys) {
518+
ensureDebugIsInitialized();
519+
const mirror = Debug.MakeMirror(value, true);
520+
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
521+
var vals = mirror.preview();
522+
var output = [];
523+
for (let o of vals) {
524+
output.push(formatValue(ctx, o, nextRecurseTimes));
525+
}
526+
return output;
527+
}
528+
504529
function formatPromise(ctx, value, recurseTimes, visibleKeys, keys) {
505530
var output = [];
506531
var internals = inspectPromise(value);

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
'src/node_javascript.cc',
116116
'src/node_main.cc',
117117
'src/node_os.cc',
118+
'src/node_util.cc',
118119
'src/node_v8.cc',
119120
'src/node_stat_watcher.cc',
120121
'src/node_watchdog.cc',

src/node_util.cc

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "node.h"
2+
#include "v8.h"
3+
#include "env.h"
4+
#include "env-inl.h"
5+
6+
namespace node {
7+
namespace util {
8+
9+
using v8::Context;
10+
using v8::FunctionCallbackInfo;
11+
using v8::Local;
12+
using v8::Object;
13+
using v8::Value;
14+
15+
static void IsMapIterator(const FunctionCallbackInfo<Value>& args) {
16+
CHECK_EQ(1, args.Length());
17+
args.GetReturnValue().Set(args[0]->IsMapIterator());
18+
}
19+
20+
21+
static void IsSetIterator(const FunctionCallbackInfo<Value>& args) {
22+
CHECK_EQ(1, args.Length());
23+
args.GetReturnValue().Set(args[0]->IsSetIterator());
24+
}
25+
26+
27+
void Initialize(Local<Object> target,
28+
Local<Value> unused,
29+
Local<Context> context) {
30+
Environment* env = Environment::GetCurrent(context);
31+
env->SetMethod(target, "isMapIterator", IsMapIterator);
32+
env->SetMethod(target, "isSetIterator", IsSetIterator);
33+
}
34+
35+
} // namespace util
36+
} // namespace node
37+
38+
NODE_MODULE_CONTEXT_AWARE_BUILTIN(util, node::util::Initialize)

test/parallel/test-util-inspect.js

+20
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,26 @@ global.Promise = function() { this.bar = 42; };
287287
assert.equal(util.inspect(new Promise()), '{ bar: 42 }');
288288
global.Promise = oldPromise;
289289

290+
// Map/Set Iterators
291+
var m = new Map([['foo', 'bar']]);
292+
assert.strictEqual(util.inspect(m.keys()), 'MapIterator { \'foo\' }');
293+
assert.strictEqual(util.inspect(m.values()), 'MapIterator { \'bar\' }');
294+
assert.strictEqual(util.inspect(m.entries()),
295+
'MapIterator { [ \'foo\', \'bar\' ] }');
296+
// make sure the iterator doesn't get consumed
297+
var keys = m.keys();
298+
assert.strictEqual(util.inspect(keys), 'MapIterator { \'foo\' }');
299+
assert.strictEqual(util.inspect(keys), 'MapIterator { \'foo\' }');
300+
301+
var s = new Set([1, 3]);
302+
assert.strictEqual(util.inspect(s.keys()), 'SetIterator { 1, 3 }');
303+
assert.strictEqual(util.inspect(s.values()), 'SetIterator { 1, 3 }');
304+
assert.strictEqual(util.inspect(s.entries()),
305+
'SetIterator { [ 1, 1 ], [ 3, 3 ] }');
306+
// make sure the iterator doesn't get consumed
307+
keys = s.keys();
308+
assert.strictEqual(util.inspect(keys), 'SetIterator { 1, 3 }');
309+
assert.strictEqual(util.inspect(keys), 'SetIterator { 1, 3 }');
290310

291311
// Test alignment of items in container
292312
// Assumes that the first numeric character is the start of an item.

0 commit comments

Comments
 (0)