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: support inspecting Map, Set, and Promise #1471

Closed
wants to merge 8 commits into from
Closed

util: support inspecting Map, Set, and Promise #1471

wants to merge 8 commits into from

Conversation

monsanto
Copy link
Contributor

Among other things, this makes working with ES6 containers bearable on the REPL.

Before:

> new Map([[1, "foo"], [null, "bar"], [{quux: 42}, undefined]])
{}

After:

> new Map([[1, "foo"], [null, "bar"], [{quux: 42}, undefined]])
Map { 1 => 'foo', null => 'bar', { quux: 42 } => undefined }

The output format is inspired by Chrome DevTools.

I would like to support inspecting promises but I do not know of a way to synchronously obtain the status and fulfillment value. If someone would like to point me in the right direction I can provide a followup PR. (see below)

The output format is inspired by Chrome DevTools.
@domenic
Copy link
Contributor

domenic commented Apr 20, 2015

I would like to support inspecting promises but I do not know of a way to synchronously obtain the status and fulfillment value.

Probably only possible via the V8 API... would be worth looking at the dev tools code to see how it's done

// If the opening "brace" is too large, like in the case of "Set {",
// we need to force the first item to be on the next line or the
// items will not line up correctly.
(base === '' && braces[0].length === 1 ? '' : base + '\n ') +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this case needs tests

@brendanashworth brendanashworth added the util Issues and PRs related to the built-in util module. label Apr 20, 2015
@@ -235,3 +235,17 @@ if (typeof Symbol !== 'undefined') {
assert.equal(util.inspect(subject, options), '[ 1, 2, 3, [length]: 3, [Symbol(symbol)]: 42 ]');

}

// test Set
assert.equal(util.inspect(new Set), 'Set {}')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: semicolons here and below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have that in a new commit yet to be pushed. Is it normal that make jshint doesn't touch tests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, for some reason that choice was made a long time ago. Wouldn't call it "normal" though :)

@monsanto
Copy link
Contributor Author

@domenic I have added a test covering the case you commented on, thanks.

There were no tests of alignment at all, so I took the luxury of adding tests for the rest of the containers as well.

@monsanto
Copy link
Contributor Author

I just pushed a proof-of-concept implementation of util.inspect for Promises. It uses the mirror debugger to synchronously inspect Promises. (Alternatively, this could be done if I could have a module with compiler intrinsics enabled). There are bits I don't like about my implementation but I wanted to get this out there before I went to bed in case people think my course of action totally sucks and I should abandon ship 😛

Output looks like

> Promise.resolve(3)
Promise { 3 }
> Promise.reject(3)
Promise { <rejected> 3 }
> new Promise(function(){})
Promise { <pending> }

which is a change from Chrome DevTools, as they are ridiculously verbose:

> Promise.resolve(3)
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 3}
> Promise.reject(3)
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 3}
> new Promise(function(){})
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}

@monsanto monsanto changed the title util: support inspecting Map and Set util: support inspecting Map, Set, and Promise Apr 20, 2015
@bnoordhuis
Copy link
Member

Left some comments on the promise inspection code. I don't disagree with the Debug.MakeMirror approach (in fact I think it's the only way to crack open a promise) but I think the current implementation is creating mirrors too eagerly.

@monsanto
Copy link
Contributor Author

@bnoordhuis Thank you for your comments.

runInDebugContext isn't cheap

I've hoisted it to the top of the file for simplicity; however if this is too expensive or otherwise unsatisfactory I can create it on demand and cache.

only way to crack open a promise

If we could have compiler intrinsics enabled for the lib/internals folder, the Promise internals are at the properties %CreateGlobalPrivateOwnSymbol("Promise#status") and ...("Promise#value"). I don't know if this is more or less desirable than using runInDebugContext. My guess is less, but perhaps this functionality would be useful for other things in the future..

Mirrors being cached

you are correct, I should have noticed this. They are now created as "transient" so they will not be cached.

creating so many mirrors

I've guarded the mirror creation with an instanceof, so the common case should not be penalized. The code still calls inspectPromise twice if it is indeed a native promise, but I can't eliminate this without a more involved refactor (or creating a closure, which seems just as bad as creating an extra mirror). I have tried to keep my changes as minimal as possible as this is my first io.js PR.

we may mutate keys in the typecase, so don't check keys.length until no
more mutations.
@@ -1,6 +1,7 @@
'use strict';

const uv = process.binding('uv');
const Debug = require('vm').runInDebugContext('Debug');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@domenic: Should we add support for Weak{Set,Map} inspection through Debug as a follow-on to this PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noooo that seems very bad, basically exposes iteration to JS.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah – I thought it might be a bit fraught with peril; was surprised to see the chrome dev tools expose that info.

@chrisdickinson
Copy link
Contributor

The nits can probably be disregarded – just looked and saw that we were using map before, anyway :)

This LGTM. Unless @bnoordhuis or @domenic objects, I'll merge this tomorrow afternoon.

@bnoordhuis
Copy link
Member

If we could have compiler intrinsics enabled for the lib/internals folder, the Promise internals are at the properties %CreateGlobalPrivateOwnSymbol("Promise#status") and ...("Promise#value").

It's possible to do that but it's kind of a hack:

const v8 = require('v8');
v8.setFlagsFromString('--allow_natives_syntax');
const promiseStatus = eval('%CreateGlobalPrivateOwnSymbol("Promise#status")');
const promiseValue = eval('%CreateGlobalPrivateOwnSymbol("Promise#value")');
v8.setFlagsFromString('--noallow_natives_syntax');

@@ -276,14 +285,44 @@ function formatValue(ctx, value, recurseTimes) {
}
}

var base = '', array = false, braces = ['{', '}'];
var base = '', empty, braces, formatter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferably assign empty = false and use strict checks below (i.e. empty = empty === true && keys.length === 0 and if (empty === true) {.)

It's a minor thing but V8 emits marginally tighter code for strict boolean comparisons.

@monsanto
Copy link
Contributor Author

@bnoordhuis @chrisdickinson OK, I addressed these style nits.

Just curious, is there a reason to keep the instanceof check out of inspectPromise?

It saves an instanceof check. I'm hoping to do a larger scale cleanup of util.inspect when I next have free time, which should fix the double inspectPromise calls and make this issue moot.

Aside: am I the only one who finds it moronic that recurseTimes is sometimes a number and sometimes not?

Yes, another thing I would like to clean up. (Or at least move the subtraction logic to a central place)

chrisdickinson pushed a commit that referenced this pull request Apr 26, 2015
@chrisdickinson
Copy link
Contributor

Merged in bf7ac08. Thanks!

@rvagg rvagg mentioned this pull request Apr 27, 2015
rvagg added a commit that referenced this pull request May 4, 2015
PR-URL: #1532

Notable Changes:

* crypto: significantly reduced memory usage for TLS (Fedor Indutny & Сковорода
  Никита Андреевич) #1529
* net: socket.connect() now accepts a 'lookup' option for a custom DNS
  resolution mechanism, defaults to dns.lookup() (Evan Lucas) #1505
* npm: Upgrade npm to 2.9.0. See the v2.8.4 and v2.9.0 release notes for
  details. Notable items:
  - Add support for default author field to make npm init -y work without
    user-input (@othiym23) npm/npm/d8eee6cf9d
  - Include local modules in npm outdated and npm update (@ArnaudRinquin)
    npm/npm#7426
  - The prefix used before the version number on npm version is now configurable
    via tag-version-prefix (@kkragenbrink) npm/npm#8014
* os: os.tmpdir() is now cross-platform consistent and will no longer returns a
  path with a trailling slash on any platform (Christian Tellnes) #747
* process:
  - process.nextTick() performance has been improved by between 2-42% across the
    benchmark suite, notable because this is heavily used across core (Brian White) #1548
  - New process.geteuid(), process.seteuid(id), process.getegid() and
    process.setegid(id) methods allow you to get and set effective UID and GID
    of the process (Evan Lucas) #1536
* repl:
  - REPL history can be persisted across sessions if the NODE_REPL_HISTORY_FILE
    environment variable is set to a user accessible file,
    NODE_REPL_HISTORY_SIZE can set the maximum history size and defaults to 1000
    (Chris Dickinson) #1513
  - The REPL can be placed in to one of three modes using the NODE_REPL_MODE
    environment variable: sloppy, strict or magic (default); the new magic mode
    will automatically run "strict mode only" statements in strict mode
    (Chris Dickinson) #1513
* smalloc: the 'smalloc' module has been deprecated due to changes coming in V8
  4.4 that will render it unusable
* util: add Promise, Map and Set inspection support (Christopher Monsanto) #1471
* V8: upgrade to 4.2.77.18, see the ChangeLog for full details. Notable items:
  - Classes have moved out of staging; the class keyword is now usable in strict
    mode without flags
  - Object literal enhancements have moved out of staging; shorthand method and
    property syntax is now usable ({ method() { }, property })
  - Rest parameters (function(...args) {}) are implemented in staging behind the
    --harmony-rest-parameters flag
  - Computed property names ({['foo'+'bar']:'bam'}) are implemented in staging
    behind the --harmony-computed-property-names flag
  - Unicode escapes ('\u{xxxx}') are implemented in staging behind the
    --harmony_unicode flag and the --harmony_unicode_regexps flag for use in
    regular expressions
* Windows:
  - Random process termination on Windows fixed (Fedor Indutny) #1512 / #1563
  - The delay-load hook introduced to fix issues with process naming (iojs.exe /
    node.exe) has been made opt-out for native add-ons. Native add-ons should
    include 'win_delay_load_hook': 'false' in their binding.gyp to disable this
    feature if they experience problems . (Bert Belder) #1433
* Governance:
  - Rod Vagg (@rvagg) was added to the Technical Committee (TC)
  - Jeremiah Senkpiel (@Fishrock123) was added to the Technical Committee (TC)
@monsanto monsanto deleted the map-set-inspect branch June 10, 2015 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
util Issues and PRs related to the built-in util module.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants