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

[wip] src, inspector: support opted-in VM contexts #14231

Closed
wants to merge 12 commits into from

Conversation

TimothyGu
Copy link
Member

@TimothyGu TimothyGu commented Jul 14, 2017

Works, doesn't crash, but no tests or docs at this moment.

V8's console is injected unconditionally upon every attachContext call, which may be not desirable.

Uses an approach inspired by @jgoz in #7593 (comment).

/cc @eugeneo @bmeck

Fixes: #7593
Refs: #9272

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

inspector

Based on work by Bradley Farias <[email protected]> and Eugene
Ostroukhov <[email protected]>.

TODO: V8's console is injected unconditionally, which may be not desirable.

Fixes: nodejs#7593
Refs: nodejs#9272
@TimothyGu TimothyGu requested review from bmeck and eugeneo July 14, 2017 17:37
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. labels Jul 14, 2017
@jgoz
Copy link

jgoz commented Jul 14, 2017

This is pretty close to a solution I started a couple days ago, but mine avoids refactoring Contextify, instead introducing a ContextFromSandbox function that is used by the inspector agent to extract a V8 context.

Anyway, I like your solution better @TimothyGu.

@jgoz
Copy link

jgoz commented Jul 14, 2017

@TimothyGu I'll push my branch and you can lift the test I wrote for it if you like. Same API.

Edit: https://github.com/jgoz/node/blob/inspector-attach-context/test/inspector/test-inspector-contexts.js

@refack
Copy link
Contributor

refack commented Jul 14, 2017

Refs: #13295, #12243
Why can't it be streamlined into vm.runInDebugContext or vm.runInInspectorContext?

@TimothyGu
Copy link
Member Author

@refack Debug context is going to be removed by the end of this year. I'm not sure what you are referring to by "Inspector context".

@TimothyGu
Copy link
Member Author

@jgoz Thanks, I'll have to look into how the inspector harness works, and your script is a good place to start!

@refack
Copy link
Contributor

refack commented Jul 15, 2017

@refack Debug context is going to be removed by the end of this year. I'm not sure what you are referring to by "Inspector context".

  1. One could argue that instead of removing vm.runInDebugContext we could fix/reimplement based on the mechanism in this PR.
  2. Alternatively you could create the Inspector variant and name it vm.runInInspectorContext and it will be a wrapper that encapsulates the logic needed.

More specifically my question is does the two new endpoint have any use outside of vm associated operations? If not maybe they should be internal/ and published only as vm.context methods?

module.exports = {
    open: (port, host, wait) => open(port, host, !!wait),
    close: process._debugEnd,
    url: url,		    url: url,
 +  Session,
 +  attachContext,
 +  detachContext
  };

Which leads me to the (trivial) observation that this PR would be easier to review if you provided a draft doc and/or some tests... 🤷‍♂️

@jgoz
Copy link

jgoz commented Jul 15, 2017

@refack

  1. One could argue that instead of removing vm.runInDebugContext we could fix/reimplement based on the mechanism in this PR.
  2. Alternatively you could create the Inspector variant and name it vm.runInInspectorContext and it will be a wrapper that encapsulates the logic needed.

More specifically my question is does the two new endpoint have any use outside of vm associated operations? If not maybe they should be internal/ and published only as vm.context methods?

Unless there is another way to create V8 contexts in Node, I think you are correct that these are only useful for vm operations. My original reasoning for adding to the inspector API, rather than vm was as follows:

  1. Avoid coupling vm to inspector/current debugging subsystem (and similarly, avoid future deprecations such as with runInDebugContext)
  2. inspector API is currently designated "unstable", so if a better solution becomes possible (e.g., via support from the V8 team), deprecating this API will not be as painful

Something worth testing would be calling attach at the start of runInContext and then detach at the end. If this allowed debugging during execution of a script while allowing proper GC cleanup of contexts as well as re-use of contexts, it might be a better solution (possibly controlled with an opt-in or opt-out flag).

@refack
Copy link
Contributor

refack commented Jul 15, 2017

  1. Avoid coupling vm to inspector/current debugging subsystem (and similarly, avoid future deprecations such as with runInDebugContext)
  2. inspector API is currently designated "unstable", so if a better solution becomes possible (e.g., via support from the V8 team), deprecating this API will not be as painful

Something worth testing would be calling attach at the start of runInContext and then detach at the end. If this allowed debugging during execution of a script while allowing proper GC cleanup of contexts as well as re-use of contexts, it might be a better solution (possibly controlled with an opt-in or opt-out flag).

A dilemma indeed...

I have some weird ideas like adding an implementation module arg to runInDebugContext so it'll be:

runInDebugContext(code, require('inspector'));

Or adding a scope method to inspector so:

require('inspector').runInScope(context);

@TimothyGu
Copy link
Member Author

@jgoz That would actually work, but only synchronously. That's still a better default behavior though, so I implemented it and added a big caveat in the documentation.

I've added tests and docs, but still need to figure out the console issue.


As I was talking to @addaleax at JSConf China, when contextCreated is called, V8 unconditionally sets the sandbox's console property to its internal console. This is possibly undesirable for most users of the VM module.

On our side I foresee three options:

  • ignore Do not make any changes to the sandbox
  • merge Merge the methods so that inspector and original methods are both called
  • replace Replace whatever console avaiable in the sandbox with the Inspector version

All three are implementable right now, but the first two are difficult to implement w/o side effects. I would prefer it if V8 adds a flag to disable this feature. I'm working on writing a CL to V8, but Google's custom tools require a bit of a setup.

@TimothyGu
Copy link
Member Author

Unfortunately, the console issue is complicated by v8/v8@54271c2.


To @ak239, @hashseed, and the rest of @nodejs/v8:

We are trying to add Inspector support for contexts created through Node.js's VM module. The way we do that can be roughly illustrated as:

const inspector = require('inspector');
const vm = require('vm');
const sandbox = {
  customProp: 42
};
vm.createContext(sandbox);
inspector.attachContext(sandbox);
vm.runInContext('newProp = customProp + 1;', sandbox);
console.log(sandbox);
// Prints { customProp: 42, newProp: 43 }

Now, V8's contextCreated callback unconditionally initializes the global object's console property to an Inspector-aware version, but this new console property would either be added to sandbox even though the user never requested it to be added, or worse, overwrite an existing sandbox.console before the inspector.attachContext call.

This is not really a problem for the main (top) context, as we already provide a global console variable anyway, and we can just merge our console methods with V8's. But it is a problem for VM contexts, whose properties can always be manipulated outside the context.

In the V8 version we currently have, the console setting is done by a context->Global()->Set, so it is reasonably easy to revert that change just for VM. But after v8/v8@54271c2, it is practically impossible to restore the original state.

Please offer some ideas about how we can and should proceed re. this issue. Thanks!

@@ -43,6 +48,10 @@ using v8_inspector::V8Inspector;
static uv_sem_t start_io_thread_semaphore;
static uv_async_t start_io_thread_async;

// Used in NodeInspectorClient::currentTimeMS() below.
const int NANOS_PER_MSEC = 1000000;
Copy link
Contributor

Choose a reason for hiding this comment

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

I've you just moved this line, but maybe do const double NANOS_PER_MSEC = 1E6; since AFAICT it's only used for double arithmetic.

Copy link
Contributor

@refack refack left a comment

Choose a reason for hiding this comment

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

High level LGTM
I'm still -0.5 on increasing the surface area of the public inspector module.

@refack
Copy link
Contributor

refack commented Jul 17, 2017

In the V8 version we currently have, the console setting is done by a context->Global()->Set, so it is reasonably easy to revert that change just for VM. But after v8/v8@54271c2, it is practically impossible to restore the original state.

Please offer some ideas about how we can and should proceed re. this issue. Thanks!

The global assignment line line from v8/v8@54271c2:

JSObject::AddProperty(global, name, console, DONT_ENUM);

AFAICT the property is non enumerable so console.log(sandbox); should not show it. It's also configurable so we could delete global.console

V8 6.1 in Chrome Canary:
image

@TimothyGu
Copy link
Member Author

Okay, the enumerable bit notwithstanding, sandbox might already have a console property defined in a different way. It could be an accessor property, for example, and setting it in any way could wreck hazard because of the side effects of the setter. Plus, one would have to save the property descriptor before going into V8 to actually restore to the correct value, and even though that's still not too complex it would be exponentially so if the console property is on the [[Prototype]] of the sandbox object, or if sandbox is a Proxy, etc.

@refack
Copy link
Contributor

refack commented Jul 17, 2017

Okay, the enumerable bit notwithstanding, sandbox might already have a console property defined in a different way. It could be an accessor property, for example, and setting it in any way could wreck hazard because of the side effects of the setter. Plus, one would have to save the property descriptor before going into V8 to actually restore to the correct value, and even though that's still not too complex it would be exponentially so if the console property is on the [[Prototype]] of the sandbox object, or if sandbox is a Proxy, etc.

Ack. Having the ability to flag V8 not to add it, is best.

@hashseed
Copy link
Member

Deleting the console property after creating the context sounds like the simplest solution. You could also get it removed in the snapshot by deleting it before creating the snapshot, but then it will be missing in the main context. That could be solved by actually serializing two separate contexts, but that adds to the snapshot size...

V8 includes the console object as builtin precisely to be able to take advantage of the snapshot.

(if support for inspector is available in the Node.js build). If not, and if
`doNotInformInspector` is not specified, this function attaches it to inspector
using [`inspector.attachContext()`][], and detaches it before returning.
However, *it is still recommended that you handle context attachment and
Copy link
Member

Choose a reason for hiding this comment

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

Please avoid the use of you in the docs :-)

(if support for inspector is available in the Node.js build). If not, and if
`doNotInformInspector` is not specified, this function attaches it to inspector
using [`inspector.attachContext()`][], and detaches it before returning.
However, *it is still recommended that you handle context attachment and
Copy link
Member

Choose a reason for hiding this comment

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

Here too... please avoid you

@refack
Copy link
Contributor

refack commented Jul 17, 2017

@hashseed what would be the implications of adding:

   Handle<JSObject> console = factory->NewJSObject(cons, TENURED);
   DCHECK(console->IsJSObject());
+ if (!global.has(name)) {
   JSObject::AddProperty(global, name, console, DONT_ENUM);
+ }

@hashseed
Copy link
Member

It doesn't work. We only create one context to capture snapshot from, which acts as template for all contexts.

The sandboxed object is not even copied into the global object, but hooked into it via interceptor, right?

We do have a step where we copy over all properties from the deserialized global object into the global object created from object template. Maybe we can hook that into that place. Iirc the interceptor is disabled during bootstrapping though?

@fhinkel



Runs the compiled code contained by the `vm.Script` object within the given
`contextifiedSandbox` and returns the result. Running code does not have access
to local scope.

By default, this function checks if inspector was already aware of the context
(if support for inspector is available in the Node.js build). If not, and if
`doNotInformInspector` is not specified, this function attaches it to inspector
Copy link

Choose a reason for hiding this comment

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

Lots of negatives in here, which might get confusing. How about something like this:

This function will attach the context to inspector (in Node.js builds with inspector support) using [inspector.attachContext()][] before running the script and detach it with [inspector.detachContext()][] before returning. The context will only be attached if inspector was not already aware of the context, or if doNotInformInspector is unspecified or false. Note that it is recommended to handle context attachment and detachment manually, since:


The `vm.runInContext()` method compiles `code`, runs it within the context of
the `contextifiedSandbox`, then returns the result. Running code does not have
access to the local scope. The `contextifiedSandbox` object *must* have been
previously [contextified][] using the [`vm.createContext()`][] method.

By default, this function checks if inspector was already aware of the context
(if support for inspector is available in the Node.js build). If not, and if
`doNotInformInspector` is not specified, this function attaches it to inspector
Copy link

Choose a reason for hiding this comment

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

Same recommended rewording as my above comment.

@jgoz
Copy link

jgoz commented Jul 17, 2017

@TimothyGu Given the caveats you outlined in the documentation for automatic attachment/detachment in vm.runInContext, would it make more sense for this behaviour to be opt-in, rather than opt-out? I realize that doesn't offer an optimal/"just works" debugging experience, but (especially with the async caveat) the current opt-out behaviour is not optimal either.

Copy link
Contributor

@eugeneo eugeneo left a comment

Choose a reason for hiding this comment

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

I strongly feel like this is not a proper fix as this really needs to be fixed in V8... What will happen to this new API if/when the bug is fixed in V8?

@@ -102,6 +137,7 @@ class Agent {
std::unique_ptr<NodeInspectorClient> client_;
std::unique_ptr<InspectorIo> io_;
v8::Platform* platform_;
std::vector<const node::inspector::ContextInfo*> contexts_;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move this to NodeInspectorClient

session->server_->MessageReceived(session->id_,
std::string(buf->base, read));
std::string str(buf->base, read);
#if DEBUG_TRANSACTION
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove this before committing.

@alexkozy
Copy link
Member

I think we need to fix it on inspector side and allow V8 embedders not to call contextDestroyed and then we can just call contextCreated when context is created in Node.js.
CL: https://chromium-review.googlesource.com/c/575519

@TimothyGu
Copy link
Member Author

@eugeneo, if you guys can fix it on the V8 side -- even better! We still need some sort of API to provide context name (since vm Module Context 56 isn't exactly clear on what it actually is) and origin info (for use cases like jsdom if they want to have first class support in inspector; /cc @domenic), though that can be done in vm.createContext().

@hashseed Right now it is actually both copied and intercepted. Because the copying is done synchronously, async in VM contexts is already not supported perfectly so I'd be fine with leaving it that way (/cc @jgoz). In fact, this copying step has led to a lot of problems. @AnnaMag is doing some work to rectify that situation in #13265. And yes, the interceptors are supposed to be disabled during bootstrapping.

@AnnaMag
Copy link
Member

AnnaMag commented Jul 18, 2017

@hashseed, @TimothyGu Sandbox object is intercepted, while the copying step happens after and is a filler only for the own properties (in the global context) that were not set on the sandbox via the interceptors.
I believe that #13265 will fix the async issues in VM (am I right about it @fhinkel ?). The patch is on hold pending V8 changes.

@eugeneo
Copy link
Contributor

eugeneo commented Jul 18, 2017

I would appreciate if you checked out https://github.com/eugeneo/node/tree/multicontext-v8-patch, as an alternative to that patch.

That branch includes a pending V8 change backported to the Node. I am testing right now, looks like it mostly works - e.g. no crashes and some context are even getting garbage collected... Remaining issues are:

  1. Console is always injected into new contexts (this is V8 behavior, not sure what could be done on the Node side).
  2. Contexts are retained if "console.log" or "debugger" statements are executed.

I am planning to start a new PR if we manage to resolve issue #2 and if the V8 changes get landed into that tree.

@TimothyGu
Copy link
Member Author

@eugeneo I'd be happy to defer to your branch if the V8 issues are resolved.

@TimothyGu
Copy link
Member Author

Closed in favor of #14465.

@TimothyGu TimothyGu closed this Aug 10, 2017
@TimothyGu TimothyGu added inspector Issues and PRs related to the V8 inspector protocol vm Issues and PRs related to the vm subsystem. labels Dec 17, 2017
@TimothyGu TimothyGu deleted the vm-inspector branch December 17, 2017 21:57
TimothyGu added a commit that referenced this pull request Dec 23, 2017
The `auxData` field is not exposed to JavaScript, as DevTools uses it
for its `isDefault` parameter, which is implemented faithfully,
contributing to the nice indentation in the context selection panel.
Without the indentation, when `Target` domain gets implemented (along
with a single Inspector for cluster) in #16627, subprocesses and VM
contexts will be mixed up, causing confusion.

PR-URL: #17720
Refs: #14231 (comment)
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: Jon Moss <[email protected]>
Reviewed-By: James M Snell <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jan 8, 2018
The `auxData` field is not exposed to JavaScript, as DevTools uses it
for its `isDefault` parameter, which is implemented faithfully,
contributing to the nice indentation in the context selection panel.
Without the indentation, when `Target` domain gets implemented (along
with a single Inspector for cluster) in #16627, subprocesses and VM
contexts will be mixed up, causing confusion.

PR-URL: #17720
Refs: #14231 (comment)
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: Jon Moss <[email protected]>
Reviewed-By: James M Snell <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jan 9, 2018
The `auxData` field is not exposed to JavaScript, as DevTools uses it
for its `isDefault` parameter, which is implemented faithfully,
contributing to the nice indentation in the context selection panel.
Without the indentation, when `Target` domain gets implemented (along
with a single Inspector for cluster) in #16627, subprocesses and VM
contexts will be mixed up, causing confusion.

PR-URL: #17720
Refs: #14231 (comment)
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: Jon Moss <[email protected]>
Reviewed-By: James M Snell <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jan 9, 2018
The `auxData` field is not exposed to JavaScript, as DevTools uses it
for its `isDefault` parameter, which is implemented faithfully,
contributing to the nice indentation in the context selection panel.
Without the indentation, when `Target` domain gets implemented (along
with a single Inspector for cluster) in #16627, subprocesses and VM
contexts will be mixed up, causing confusion.

PR-URL: #17720
Refs: #14231 (comment)
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: Jon Moss <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. inspector Issues and PRs related to the V8 inspector protocol lib / src Issues and PRs related to general changes in the lib or src directory. vm Issues and PRs related to the vm subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants