-
Notifications
You must be signed in to change notification settings - Fork 30k
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
inspector: Allow require in Runtime.evaluate #8837
Conversation
I wasn't sure if this warrants a mention in the docs. So far I assume that |
lib/internal/bootstrap_node.js
Outdated
@@ -225,12 +225,27 @@ | |||
global.setTimeout = timers.setTimeout; | |||
} | |||
|
|||
function setupConsoleAPI(addCommandLineAPIMethod) { | |||
addCommandLineAPIMethod('require', function require(request) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing, this could just access process.inspector.addCommandLineAPIMethod
directly, whcih would be preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I would move this below setupGlobalConsole
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved setupConsoleAPI
below and renamed it to setupInspectorAPI
(closer to inspector.addCommandLineAPIMethod
). Also switched to accessing the method from process instead of passing it.
950a6e0
to
4d578e8
Compare
lib/internal/bootstrap_node.js
Outdated
@@ -249,6 +250,21 @@ | |||
}); | |||
} | |||
|
|||
function setupInspectorAPI() { | |||
var addCommandLineAPIMethod = process.inspector.addCommandLineAPIMethod; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: const
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
test/inspector/test-inspector.js
Outdated
@@ -151,6 +151,37 @@ function testInspectScope(session) { | |||
]); | |||
} | |||
|
|||
function testCommandLineAPI(session) { | |||
console.log('[test]', 'Verify that command line API was injected'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'd prefer if the tests did not include unnecessary console.log statements
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here - should I remove the console.log
from the other functions in this file as well? Only added it for consistency with the existing code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the console.log
I added already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM if CI is happy and @Fishrock123's concerns are met.
4d578e8
to
aa5f323
Compare
src/inspector_agent.cc
Outdated
|
||
void AgentImpl::AddAPIMethod(const v8::FunctionCallbackInfo<v8::Value>& info) { | ||
auto env = Environment::GetCurrent(info); | ||
v8::Local<v8::Object> console_api = env->inspector_console_api_object(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you check arguments here and ThrowError if something wrong in the same way like in InspectorWrapConsoleCall?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
src/inspector_agent.cc
Outdated
@@ -573,6 +585,16 @@ void AgentImpl::InstallInspectorOnProcess() { | |||
v8::Local<v8::Object> inspector = v8::Object::New(env->isolate()); | |||
READONLY_PROPERTY(process, "inspector", inspector); | |||
env->SetMethod(inspector, "wrapConsoleCall", InspectorWrapConsoleCall); | |||
env->SetMethod(inspector, "addCommandLineAPIMethod", AddAPIMethod); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'd prefer InspectorAddCommandLineAPIMethod or AddCommandLineAPIMethod as a name of this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the method name might have gotten shorter after I discovered the 80 char limit... Went back to a more descriptive name & more line breaks.
src/env.h
Outdated
@@ -240,6 +240,7 @@ namespace node { | |||
V(domains_stack_array, v8::Array) \ | |||
V(fs_stats_constructor_function, v8::Function) \ | |||
V(generic_internal_field_template, v8::ObjectTemplate) \ | |||
V(inspector_console_api_object, v8::Object) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to check, is this object accessible from JS code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't an object per-se, it is not exposed to JS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @Fishrock123 said, this is just used as a bucket of named persistent values from native code, it's never directly exposed to JS.
lib/internal/bootstrap_node.js
Outdated
@@ -231,6 +231,7 @@ | |||
if (process.inspector) { | |||
inspectorConsole = global.console; | |||
wrapConsoleCall = process.inspector.wrapConsoleCall; | |||
setupInspectorAPI(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'd prefer API -> commandLineAPI. API sounds too broad for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nah, I think this is fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm with @ak239, setupInspectorCommandLineAPI()
is a more descriptive name.
lib/internal/bootstrap_node.js
Outdated
@@ -249,6 +250,21 @@ | |||
}); | |||
} | |||
|
|||
function setupInspectorAPI() { | |||
var addCommandLineAPIMethod = process.inspector.addCommandLineAPIMethod; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove this method from inspector object right here to be sure that it won't leak?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, will do.
src/inspector_agent.cc
Outdated
@@ -253,6 +253,7 @@ class AgentImpl { | |||
static void WriteCbIO(uv_async_t* async); | |||
|
|||
void InstallInspectorOnProcess(); | |||
static void AddAPIMethod(const v8::FunctionCallbackInfo<v8::Value>& info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you define this method outside of AgentImpl?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this out and renamed (see below).
lib/internal/bootstrap_node.js
Outdated
@@ -249,6 +250,21 @@ | |||
}); | |||
} | |||
|
|||
function setupInspectorAPI() { | |||
const addCommandLineAPIMethod = process.inspector.addCommandLineAPIMethod; | |||
addCommandLineAPIMethod('require', function require(request) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this callback called synchronously? if not, I need to inspect the startup timing a bit more
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line you're commenting on is executed synchronously during bootup. From my understanding that should prevent any debug client from connecting (and calling Runtime.evaluate
) before we registered the API method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me.
Sorry for force-pushing the last couple of times, missed that sentence in the contribution guide. Pushed a commit to address the last round of feedback. |
@jkrems I don't think force-pushing is a problem, AFAIK it's a matter of personal preference. I like to use |
thanks, lgtm |
src/inspector_agent.cc
Outdated
@@ -567,12 +578,30 @@ void AgentImpl::WaitForDisconnect() { | |||
v8::ReadOnly).FromJust(); \ | |||
} while (0) | |||
|
|||
void AddCommandLineAPIMethod(const v8::FunctionCallbackInfo<v8::Value>& info) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be static
, right? If so, it should probably be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My C++ is a bit rusty, so the following might be just me missing one of the "things static
can mean": It's no longer a class member but a top-level function as of the latest commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkrems Yeah, for top-level functions (i.e. in namespace scope, like this one), adding static
basically means that the function will only be visible in the file in which it is defined. I think it’s generally good practice to have any top-level functions either appear in a header file to make clear that they are shared between files/part of the public C++ API, or be static
to indicate that they are intended to be local. You can grep src/
for FunctionCallbackInfo<Value>& args
, most of these functions are static.
(Also, yeah, no doubt that assigning multiple distinct meanings to static
is not an ideal thing for C++. 😄)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why I have a mild preference for inline
. It does the same thing as static
but there is never any ambiguity as to whether it gets applied to a function or a variable declaration because it's only valid for the former. Helpful when grepping around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@addaleax & @bnoordhuis: Thanks a lot for the explanation. I'm pretty sure I learned & forgot this before. Once or twice.
I updated this function and also made the other two exposed C++ methods (InspectorConsoleCall
/InspectorWrapConsoleCall
) static so the style is consistent inside of the file. Went with static
over inline
because it seemed to be the dominant style in other *.cc
files.
src/inspector_agent.cc
Outdated
auto env = Environment::GetCurrent(info); | ||
|
||
if (info.Length() != 2 || !info[0]->IsString() || !info[1]->IsFunction()) { | ||
return env->ThrowError("inspector.addCommandLineAPIMethod takes exactly " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
env->ThrowTypeError
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so but it was using ThrowError
for the same thing above. Should I change both or just my copy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkrems It’s probably okay change both. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
@gibfahn Here's the quote I was referring to:
|
Anything left to address here? |
c133999
to
83c7a88
Compare
6cce8d3
to
92a3510
Compare
Rebased on |
lib/internal/bootstrap_node.js
Outdated
@@ -231,6 +231,7 @@ | |||
if (process.inspector) { | |||
inspectorConsole = global.console; | |||
wrapConsoleCall = process.inspector.wrapConsoleCall; | |||
setupInspectorAPI(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm with @ak239, setupInspectorCommandLineAPI()
is a more descriptive name.
lib/internal/bootstrap_node.js
Outdated
module.filename = path.join(cwd, name); | ||
module.paths = Module._nodeModulePaths(cwd); | ||
return module.require(request); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't module
be defined outside the function? I think this breaks require('foo') === require('foo')
, i.e., that the second require returns the cached module instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The module cache is maintained by Module
, so individual module instances shouldn't matter. It does influence module.parent
(e.g. two modules that were loaded initially using the global require will not share the same module.parent
). But that seemed less important.
Adding a test case to verify.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somebody is bound to run into that sooner or later. If it's easy to avoid, I'd fix it now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somebody is bound to run into that sooner or later.
Can you clarify what "it" is in this context? There's now a test case that verifies that it doesn't break require('foo') === require('foo')
, unless I misunderstood your initial concern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my initial concern, yes, but you mentioned that the .parent property will be different for each require() call. If that's easy to avoid, I'd do it now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, will add additional tests for require('foo').parent === require('bar').parent
and fix this.
src/inspector_agent.cc
Outdated
for (uint32_t i = 0; i < properties->Length(); ++i) { | ||
v8::Local<v8::Value> key = properties->Get(i); | ||
target->Set(key, console_api->Get(key)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use the GetOwnPropertyNames, Get and Set overloads that take a context
as their first argument and return a Maybe/MaybeLocal?
src/inspector_agent.cc
Outdated
} | ||
|
||
v8::Local<v8::Object> console_api = env->inspector_console_api_object(); | ||
console_api->Set(info[0], info[1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
test/inspector/test-inspector.js
Outdated
}, (message) => assert.strictEqual('object', message['result']['value']) | ||
], | ||
]); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you check that:
require(name)
twice returns the same object, and- that
delete require.cache[require.resolve(name)]
followed by require again returns a new object, and - that calling require again returns the same object?
name
could be e.g. const name = path.join(common.fixturesDir, 'empty');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try. I'm not sure if require.cache
is exposed globally by this PR. Since the require goes through the normal module.require
, is there any reason to believe that deleting the require cache won't affect it?
Updated, went with adding it as another commit because consistency. |
I'm willing to take this up and fix whatever is needed to get this in, so reopening. |
6a6619c
to
268790f
Compare
@bnoordhuis Comments addressed, so PTAL. |
lib/internal/bootstrap_node.js
Outdated
consoleAPIModule.paths = Module._nodeModulePaths(cwd); | ||
const consoleAPIModule = new Module('<inspector console>'); | ||
consoleAPIModule.paths = | ||
Module._nodeModulePaths(cwd).concat(Module.globalPaths); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Teeny tiny nit: four space indent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bnoordhuis Does the four-space indent rule apply even to JS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used pretty consistently in both JS and C++ code. In fact, I thought the JS linter enforced it, even.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/buffer.js consistently uses 2-space indentation (grep for =$
), but either way it's fine for me.
268790f
to
a77c0fa
Compare
a77c0fa
to
0a183cd
Compare
Rebased. I intend to land this soon if CI doesn't have any problems with that. |
Landed in 5fd2f03. |
Some parts were written by Timothy Gu <[email protected]>. PR-URL: #8837 Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Aleksei Koziatinskii <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Some parts were written by Timothy Gu <[email protected]>. PR-URL: #8837 Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Aleksei Koziatinskii <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Notable changes * **Inspector** * `require()` is available in the inspector console now. [#8837](#8837) * **N-API** * New APIs for creating number values have been introduced. [#14573](#14573) * **Stream** * For `Duplex` streams, the high water mark option can now be set independently for the readable and the writable side. [#14636](#14636) * **Util** * `util.format` now supports the `%o` and `%O` specifiers for printing objects. [#14558](#14558) PR-URL: #14811
Notable changes * **HTTP2** * Experimental support for the built-in `http2` has been added via the `--expose-http2` flag. [#14239](#14239) * **Inspector** * `require()` is available in the inspector console now. [#8837](#8837) * Multiple contexts, as created by the `vm` module, are supported now. [#14465](#14465) * **N-API** * New APIs for creating number values have been introduced. [#14573](#14573) * **Stream** * For `Duplex` streams, the high water mark option can now be set independently for the readable and the writable side. [#14636](#14636) * **Util** * `util.format` now supports the `%o` and `%O` specifiers for printing objects. [#14558](#14558) PR-URL: #14811
Notable changes * **HTTP2** * Experimental support for the built-in `http2` has been added via the `--expose-http2` flag. [#14239](#14239) * **Inspector** * `require()` is available in the inspector console now. [#8837](#8837) * Multiple contexts, as created by the `vm` module, are supported now. [#14465](#14465) * **N-API** * New APIs for creating number values have been introduced. [#14573](#14573) * **Stream** * For `Duplex` streams, the high water mark option can now be set independently for the readable and the writable side. [#14636](#14636) * **Util** * `util.format` now supports the `%o` and `%O` specifiers for printing objects. [#14558](#14558) PR-URL: #14811
Notable changes * **HTTP2** * Experimental support for the built-in `http2` has been added via the `--expose-http2` flag. [#14239](nodejs/node#14239) * **Inspector** * `require()` is available in the inspector console now. [#8837](nodejs/node#8837) * Multiple contexts, as created by the `vm` module, are supported now. [#14465](nodejs/node#14465) * **N-API** * New APIs for creating number values have been introduced. [#14573](nodejs/node#14573) * **Stream** * For `Duplex` streams, the high water mark option can now be set independently for the readable and the writable side. [#14636](nodejs/node#14636) * **Util** * `util.format` now supports the `%o` and `%O` specifiers for printing objects. [#14558](nodejs/node#14558) PR-URL: nodejs/node#14811
Release team were -1 on landing on v6.x, if you disagree let us know. |
Checklist
make -j8 test
(UNIX), orvcbuild test nosign
(Windows) passesAffected core subsystem(s)
inspector
Description of change
Being able to require modules from the console can be very handy while debugging issues. This allows to do it even when require is no longer in scope or the process isn't currently paused. It also creates the foundation for adding other command line API methods in the future (if more use cases come up).