inspector: allow require in Runtime.evaluate
Some parts were written by Timothy Gu <timothygu99@gmail.com>. PR-URL: https://github.com/nodejs/node/pull/8837 Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Aleksei Koziatinskii <ak239spb@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Timothy Gu <timothygu99@gmail.com>
This commit is contained in:
parent
e96ca62480
commit
5fd2f03b16
17
lib/internal/bootstrap_node.js
vendored
17
lib/internal/bootstrap_node.js
vendored
@ -282,6 +282,7 @@
|
|||||||
return console;
|
return console;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setupInspectorCommandLineAPI();
|
||||||
}
|
}
|
||||||
|
|
||||||
function installInspectorConsole(globalConsole) {
|
function installInspectorConsole(globalConsole) {
|
||||||
@ -310,6 +311,22 @@
|
|||||||
return wrappedConsole;
|
return wrappedConsole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupInspectorCommandLineAPI() {
|
||||||
|
const { addCommandLineAPI } = process.binding('inspector');
|
||||||
|
if (!addCommandLineAPI) return;
|
||||||
|
|
||||||
|
const Module = NativeModule.require('module');
|
||||||
|
const { makeRequireFunction } = NativeModule.require('internal/module');
|
||||||
|
const path = NativeModule.require('path');
|
||||||
|
const cwd = tryGetCwd(path);
|
||||||
|
|
||||||
|
const consoleAPIModule = new Module('<inspector console>');
|
||||||
|
consoleAPIModule.paths =
|
||||||
|
Module._nodeModulePaths(cwd).concat(Module.globalPaths);
|
||||||
|
|
||||||
|
addCommandLineAPI('require', makeRequireFunction(consoleAPIModule));
|
||||||
|
}
|
||||||
|
|
||||||
function setupProcessFatal() {
|
function setupProcessFatal() {
|
||||||
const async_wrap = process.binding('async_wrap');
|
const async_wrap = process.binding('async_wrap');
|
||||||
// Arrays containing hook flags and ids for async_hook calls.
|
// Arrays containing hook flags and ids for async_hook calls.
|
||||||
|
@ -292,6 +292,7 @@ namespace node {
|
|||||||
V(context, v8::Context) \
|
V(context, v8::Context) \
|
||||||
V(domain_array, v8::Array) \
|
V(domain_array, v8::Array) \
|
||||||
V(domains_stack_array, v8::Array) \
|
V(domains_stack_array, v8::Array) \
|
||||||
|
V(inspector_console_api_object, v8::Object) \
|
||||||
V(jsstream_constructor_template, v8::FunctionTemplate) \
|
V(jsstream_constructor_template, v8::FunctionTemplate) \
|
||||||
V(module_load_list_array, v8::Array) \
|
V(module_load_list_array, v8::Array) \
|
||||||
V(pbkdf2_constructor_template, v8::ObjectTemplate) \
|
V(pbkdf2_constructor_template, v8::ObjectTemplate) \
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
namespace node {
|
namespace node {
|
||||||
namespace inspector {
|
namespace inspector {
|
||||||
namespace {
|
namespace {
|
||||||
|
using v8::Array;
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
using v8::External;
|
using v8::External;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
@ -554,6 +555,20 @@ class NodeInspectorClient : public V8InspectorClient {
|
|||||||
return env_->context();
|
return env_->context();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void installAdditionalCommandLineAPI(Local<Context> context,
|
||||||
|
Local<Object> target) override {
|
||||||
|
Local<Object> console_api = env_->inspector_console_api_object();
|
||||||
|
|
||||||
|
Local<Array> properties =
|
||||||
|
console_api->GetOwnPropertyNames(context).ToLocalChecked();
|
||||||
|
for (uint32_t i = 0; i < properties->Length(); ++i) {
|
||||||
|
Local<Value> key = properties->Get(context, i).ToLocalChecked();
|
||||||
|
target->Set(context,
|
||||||
|
key,
|
||||||
|
console_api->Get(context, key).ToLocalChecked()).FromJust();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void FatalException(Local<Value> error, Local<v8::Message> message) {
|
void FatalException(Local<Value> error, Local<v8::Message> message) {
|
||||||
Local<Context> context = env_->context();
|
Local<Context> context = env_->context();
|
||||||
|
|
||||||
@ -682,6 +697,20 @@ bool Agent::StartIoThread(bool wait_for_connect) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AddCommandLineAPI(
|
||||||
|
const FunctionCallbackInfo<Value>& info) {
|
||||||
|
auto env = Environment::GetCurrent(info);
|
||||||
|
Local<Context> context = env->context();
|
||||||
|
|
||||||
|
if (info.Length() != 2 || !info[0]->IsString()) {
|
||||||
|
return env->ThrowTypeError("inspector.addCommandLineAPI takes "
|
||||||
|
"exactly 2 arguments: a string and a value.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> console_api = env->inspector_console_api_object();
|
||||||
|
console_api->Set(context, info[0], info[1]).FromJust();
|
||||||
|
}
|
||||||
|
|
||||||
void Agent::Stop() {
|
void Agent::Stop() {
|
||||||
if (io_ != nullptr) {
|
if (io_ != nullptr) {
|
||||||
io_->Stop();
|
io_->Stop();
|
||||||
@ -784,8 +813,16 @@ void Url(const FunctionCallbackInfo<Value>& args) {
|
|||||||
void Agent::InitInspector(Local<Object> target, Local<Value> unused,
|
void Agent::InitInspector(Local<Object> target, Local<Value> unused,
|
||||||
Local<Context> context, void* priv) {
|
Local<Context> context, void* priv) {
|
||||||
Environment* env = Environment::GetCurrent(context);
|
Environment* env = Environment::GetCurrent(context);
|
||||||
|
{
|
||||||
|
auto obj = Object::New(env->isolate());
|
||||||
|
auto null = Null(env->isolate());
|
||||||
|
CHECK(obj->SetPrototype(context, null).FromJust());
|
||||||
|
env->set_inspector_console_api_object(obj);
|
||||||
|
}
|
||||||
|
|
||||||
Agent* agent = env->inspector_agent();
|
Agent* agent = env->inspector_agent();
|
||||||
env->SetMethod(target, "consoleCall", InspectorConsoleCall);
|
env->SetMethod(target, "consoleCall", InspectorConsoleCall);
|
||||||
|
env->SetMethod(target, "addCommandLineAPI", AddCommandLineAPI);
|
||||||
if (agent->debug_options_.wait_for_connect())
|
if (agent->debug_options_.wait_for_connect())
|
||||||
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
|
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
|
||||||
env->SetMethod(target, "connect", ConnectJSBindingsSession);
|
env->SetMethod(target, "connect", ConnectJSBindingsSession);
|
||||||
|
@ -34,6 +34,11 @@ function checkBadPath(err, response) {
|
|||||||
assert(/WebSockets request was expected/.test(err.response));
|
assert(/WebSockets request was expected/.test(err.response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkException(message) {
|
||||||
|
assert.strictEqual(message['exceptionDetails'], undefined,
|
||||||
|
'An exception occurred during execution');
|
||||||
|
}
|
||||||
|
|
||||||
function expectMainScriptSource(result) {
|
function expectMainScriptSource(result) {
|
||||||
const expected = helper.mainScriptSource();
|
const expected = helper.mainScriptSource();
|
||||||
const source = result['scriptSource'];
|
const source = result['scriptSource'];
|
||||||
@ -209,6 +214,142 @@ function testI18NCharacters(session) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testCommandLineAPI(session) {
|
||||||
|
const testModulePath = require.resolve('../fixtures/empty.js');
|
||||||
|
const testModuleStr = JSON.stringify(testModulePath);
|
||||||
|
const printAModulePath = require.resolve('../fixtures/printA.js');
|
||||||
|
const printAModuleStr = JSON.stringify(printAModulePath);
|
||||||
|
const printBModulePath = require.resolve('../fixtures/printB.js');
|
||||||
|
const printBModuleStr = JSON.stringify(printBModulePath);
|
||||||
|
session.sendInspectorCommands([
|
||||||
|
[ // we can use `require` outside of a callframe with require in scope
|
||||||
|
{
|
||||||
|
'method': 'Runtime.evaluate', 'params': {
|
||||||
|
'expression': 'typeof require("fs").readFile === "function"',
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.strictEqual(message['result']['value'], true);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // the global require has the same properties as a normal `require`
|
||||||
|
{
|
||||||
|
'method': 'Runtime.evaluate', 'params': {
|
||||||
|
'expression': [
|
||||||
|
'typeof require.resolve === "function"',
|
||||||
|
'typeof require.extensions === "object"',
|
||||||
|
'typeof require.cache === "object"'
|
||||||
|
].join(' && '),
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.strictEqual(message['result']['value'], true);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // `require` twice returns the same value
|
||||||
|
{
|
||||||
|
'method': 'Runtime.evaluate', 'params': {
|
||||||
|
// 1. We require the same module twice
|
||||||
|
// 2. We mutate the exports so we can compare it later on
|
||||||
|
'expression': `
|
||||||
|
Object.assign(
|
||||||
|
require(${testModuleStr}),
|
||||||
|
{ old: 'yes' }
|
||||||
|
) === require(${testModuleStr})`,
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.strictEqual(message['result']['value'], true);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // after require the module appears in require.cache
|
||||||
|
{
|
||||||
|
'method': 'Runtime.evaluate', 'params': {
|
||||||
|
'expression': `JSON.stringify(
|
||||||
|
require.cache[${testModuleStr}].exports
|
||||||
|
)`,
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.deepStrictEqual(JSON.parse(message['result']['value']),
|
||||||
|
{ old: 'yes' });
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // remove module from require.cache
|
||||||
|
{
|
||||||
|
'method': 'Runtime.evaluate', 'params': {
|
||||||
|
'expression': `delete require.cache[${testModuleStr}]`,
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.strictEqual(message['result']['value'], true);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // require again, should get fresh (empty) exports
|
||||||
|
{
|
||||||
|
'method': 'Runtime.evaluate', 'params': {
|
||||||
|
'expression': `JSON.stringify(require(${testModuleStr}))`,
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.deepStrictEqual(JSON.parse(message['result']['value']), {});
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // require 2nd module, exports an empty object
|
||||||
|
{
|
||||||
|
'method': 'Runtime.evaluate', 'params': {
|
||||||
|
'expression': `JSON.stringify(require(${printAModuleStr}))`,
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.deepStrictEqual(JSON.parse(message['result']['value']), {});
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // both modules end up with the same module.parent
|
||||||
|
{
|
||||||
|
'method': 'Runtime.evaluate', 'params': {
|
||||||
|
'expression': `JSON.stringify({
|
||||||
|
parentsEqual:
|
||||||
|
require.cache[${testModuleStr}].parent ===
|
||||||
|
require.cache[${printAModuleStr}].parent,
|
||||||
|
parentId: require.cache[${testModuleStr}].parent.id,
|
||||||
|
})`,
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.deepStrictEqual(JSON.parse(message['result']['value']), {
|
||||||
|
parentsEqual: true,
|
||||||
|
parentId: '<inspector console>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // the `require` in the module shadows the command line API's `require`
|
||||||
|
{
|
||||||
|
'method': 'Debugger.evaluateOnCallFrame', 'params': {
|
||||||
|
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
|
||||||
|
'expression': `(
|
||||||
|
require(${printBModuleStr}),
|
||||||
|
require.cache[${printBModuleStr}].parent.id
|
||||||
|
)`,
|
||||||
|
'includeCommandLineAPI': true
|
||||||
|
}
|
||||||
|
}, (message) => {
|
||||||
|
checkException(message);
|
||||||
|
assert.notStrictEqual(message['result']['value'],
|
||||||
|
'<inspector console>');
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
function testWaitsForFrontendDisconnect(session, harness) {
|
function testWaitsForFrontendDisconnect(session, harness) {
|
||||||
console.log('[test]', 'Verify node waits for the frontend to disconnect');
|
console.log('[test]', 'Verify node waits for the frontend to disconnect');
|
||||||
session.sendInspectorCommands({ 'method': 'Debugger.resume' })
|
session.sendInspectorCommands({ 'method': 'Debugger.resume' })
|
||||||
@ -231,6 +372,7 @@ function runTests(harness) {
|
|||||||
testSetBreakpointAndResume,
|
testSetBreakpointAndResume,
|
||||||
testInspectScope,
|
testInspectScope,
|
||||||
testI18NCharacters,
|
testI18NCharacters,
|
||||||
|
testCommandLineAPI,
|
||||||
testWaitsForFrontendDisconnect
|
testWaitsForFrontendDisconnect
|
||||||
]).expectShutDown(55);
|
]).expectShutDown(55);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user