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
45 changes: 43 additions & 2 deletions lib/inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

const EventEmitter = require('events');
const util = require('util');
const { connect, open, url } = process.binding('inspector');
const { isContext } = process.binding('contextify');
const {
connect,
open,
url,
attachContext: _attachContext,
detachContext: _detachContext
} = process.binding('inspector');

if (!connect)
throw new Error('Inspector is not available');
Expand Down Expand Up @@ -86,9 +93,43 @@ class Session extends EventEmitter {
}
}

function checkSandbox(sandbox) {
if (typeof sandbox !== 'object') {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'sandbox',
'object', sandbox);
}
if (!isContext(sandbox)) {
throw new errors.TypeError('ERR_SANDBOX_NOT_CONTEXTIFIED');
}
}

let ctxIdx = 1;
function attachContext(sandbox, {
name = `vm Module Context ${ctxIdx}`,
origin
} = {}) {
checkSandbox(sandbox);
if (typeof name !== 'string') {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options.name',
'string', name);
}
if (origin !== undefined && typeof origin !== 'string') {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options.origin',
'string', origin);
}
_attachContext(sandbox, name, origin);
}

function detachContext(sandbox) {
checkSandbox(sandbox);
_detachContext(sandbox);
}

module.exports = {
open: (port, host, wait) => open(port, host, !!wait),
close: process._debugEnd,
url: url,
Session
Session,
attachContext,
detachContext
};
2 changes: 2 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function');
E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object');
E('ERR_NO_CRYPTO', 'Node.js is not compiled with OpenSSL crypto support');
E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s');
E('ERR_SANDBOX_NOT_CONTEXTIFIED',
'Provided sandbox must have been converted to a context')
E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound');
E('ERR_SOCKET_BAD_TYPE',
'Bad socket type specified. Valid types are: udp4, udp6');
Expand Down
3 changes: 3 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
'src/node_config.cc',
'src/node_constants.cc',
'src/node_contextify.cc',
'src/node_contextify.h',
'src/node_debug_options.cc',
'src/node_file.cc',
'src/node_http_parser.cc',
Expand Down Expand Up @@ -624,8 +625,10 @@
'<(OBJ_PATH)<(OBJ_SEPARATOR)env.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)node.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_buffer.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_contextify.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_i18n.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_url.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_watchdog.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)util.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)string_bytes.<(OBJ_SUFFIX)',
'<(OBJ_PATH)<(OBJ_SEPARATOR)string_search.<(OBJ_SUFFIX)',
Expand Down
131 changes: 121 additions & 10 deletions src/inspector_agent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
#include "env.h"
#include "env-inl.h"
#include "node.h"
#include "node_contextify.h"
#include "v8-inspector.h"
#include "v8-platform.h"
#include "util.h"
#include "zlib.h"

#include "libplatform/libplatform.h"

#include <algorithm>
#include <string.h>
#include <vector>

Expand All @@ -21,6 +23,9 @@
namespace node {
namespace inspector {
namespace {

using node::contextify::ContextifyContext;

using v8::Context;
using v8::External;
using v8::Function;
Expand All @@ -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.

const int CONTEXT_GROUP_ID = 1;

class StartIoTask : public v8::Task {
public:
explicit StartIoTask(Agent* agent) : agent(agent) {}
Expand Down Expand Up @@ -376,9 +385,62 @@ void CallAndPauseOnStart(
}
}

// Used in NodeInspectorClient::currentTimeMS() below.
const int NANOS_PER_MSEC = 1000000;
const int CONTEXT_GROUP_ID = 1;
void AttachContext(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (!args[0]->IsObject()) {
env->ThrowTypeError("sandbox must be an object");
return;
}
Local<Object> sandbox = args[0].As<Object>();
ContextifyContext* contextify_context =
ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
if (contextify_context == nullptr) {
return env->ThrowTypeError(
"sandbox argument must have been converted to a context.");
}

if (contextify_context->context().IsEmpty())
return;

const char* name =
args[1]->IsString() ?
Utf8Value(env->isolate(), args[1]).out() :
"vm Module Context";
const char* origin =
args[2]->IsString() ?
Utf8Value(env->isolate(), args[2]).out() :
nullptr;

// TODO(TimothyGu): Don't allow customizing group ID for now; not sure what
// it's used for.
int group_id = CONTEXT_GROUP_ID;

auto info = new node::inspector::ContextInfo(
contextify_context->context(), group_id, name, origin,
"{\"isDefault\":false}");
// Ignore error.
env->inspector_agent()->ContextCreated(info);
}

void DetachContext(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (!args[0]->IsObject()) {
env->ThrowTypeError("sandbox must be an object");
return;
}
Local<Object> sandbox = args[0].As<Object>();
ContextifyContext* contextify_context =
ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
if (contextify_context == nullptr) {
return env->ThrowTypeError(
"sandbox argument must have been converted to a context.");
}

if (contextify_context->context().IsEmpty())
return;

env->inspector_agent()->ContextDestroyed(contextify_context->context());
}

class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
public:
Expand Down Expand Up @@ -459,11 +521,24 @@ class NodeInspectorClient : public v8_inspector::V8InspectorClient {
return uv_hrtime() * 1.0 / NANOS_PER_MSEC;
}

void contextCreated(Local<Context> context, const std::string& name) {
std::unique_ptr<StringBuffer> name_buffer = Utf8ToStringView(name);
v8_inspector::V8ContextInfo info(context, CONTEXT_GROUP_ID,
name_buffer->string());
client_->contextCreated(info);
void contextCreated(const node::inspector::ContextInfo* info) {
std::unique_ptr<StringBuffer> name_buffer = Utf8ToStringView(info->name());
v8_inspector::V8ContextInfo v8_info(info->context(env_->isolate()),
info->group_id(),
name_buffer->string());

std::unique_ptr<StringBuffer> origin_buffer;
std::unique_ptr<StringBuffer> aux_data_buffer;
if (info->origin() != nullptr) {
origin_buffer = Utf8ToStringView(info->origin());
v8_info.origin = origin_buffer->string();
}
if (info->aux_data() != nullptr) {
aux_data_buffer = Utf8ToStringView(info->aux_data());
v8_info.auxData = aux_data_buffer->string();
}

client_->contextCreated(v8_info);
}

void contextDestroyed(Local<Context> context) {
Expand Down Expand Up @@ -546,14 +621,46 @@ Agent::Agent(Environment* env) : parent_env_(env),
Agent::~Agent() {
}

bool Agent::ContextCreated(const node::inspector::ContextInfo* info) {
auto isolate = parent_env_->isolate();
auto it = std::find_if(
contexts_.begin(), contexts_.end(),
[&] (const node::inspector::ContextInfo*& cur) {
return cur->context(isolate) == info->context(isolate);
});
if (it != contexts_.end()) {
return false;
}
contexts_.push_back(info);
client_->contextCreated(info);
return true;
}

void Agent::ContextDestroyed(Local<Context> context) {
auto it = std::find_if(
contexts_.begin(), contexts_.end(),
[&] (const node::inspector::ContextInfo*& info) {
return info->context(parent_env_->isolate()) == context;
});
if (it == contexts_.end()) {
return;
}
delete *it;
contexts_.erase(it);
client_->contextDestroyed(context);
}

bool Agent::Start(v8::Platform* platform, const char* path,
const DebugOptions& options) {
path_ = path == nullptr ? "" : path;
debug_options_ = options;
client_ =
std::unique_ptr<NodeInspectorClient>(
new NodeInspectorClient(parent_env_, platform));
client_->contextCreated(parent_env_->context(), "Node.js Main Context");
CHECK(ContextCreated(
new node::inspector::ContextInfo(
parent_env_->context(), CONTEXT_GROUP_ID, "Node.js Main Context",
nullptr, "{\"isDefault\":true}")));
platform_ = platform;
CHECK_EQ(0, uv_async_init(uv_default_loop(),
&start_io_thread_async,
Expand Down Expand Up @@ -627,7 +734,9 @@ bool Agent::IsConnected() {

void Agent::WaitForDisconnect() {
CHECK_NE(client_, nullptr);
client_->contextDestroyed(parent_env_->context());
for (const node::inspector::ContextInfo*& info : contexts_) {
ContextDestroyed(info->context(parent_env_->isolate()));
}
if (io_ != nullptr) {
io_->WaitForDisconnect();
}
Expand Down Expand Up @@ -713,6 +822,8 @@ void Agent::InitInspector(Local<Object> target, Local<Value> unused,
Environment* env = Environment::GetCurrent(context);
Agent* agent = env->inspector_agent();
env->SetMethod(target, "consoleCall", InspectorConsoleCall);
env->SetMethod(target, "attachContext", AttachContext);
env->SetMethod(target, "detachContext", DetachContext);
if (agent->debug_options_.wait_for_connect())
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
env->SetMethod(target, "connect", ConnectJSBindingsSession);
Expand Down
35 changes: 35 additions & 0 deletions src/inspector_agent.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
#define SRC_INSPECTOR_AGENT_H_

#include <memory>
#include <vector>

#include <stddef.h>

#if !HAVE_INSPECTOR
#error("This header can only be used when inspector is enabled")
#endif

#include "v8.h"
#include "node_debug_options.h"

// Forward declaration to break recursive dependency chain with src/env.h.
Expand Down Expand Up @@ -46,6 +48,35 @@ class InspectorSessionDelegate {
class InspectorIo;
class NodeInspectorClient;

class ContextInfo {
public:
explicit ContextInfo(v8::Local<v8::Context> context, const int group_id,
const char* name, const char* origin = nullptr,
const char* aux_data = nullptr)
: group_id_(group_id),
name_(name),
origin_(origin),
aux_data_(aux_data) {
context_.Reset(context->GetIsolate(), context);
}

inline v8::Local<v8::Context> context(v8::Isolate* isolate) const {
return context_.Get(isolate);
}

int group_id() const { return group_id_; }
const char* name() const { return name_; }
const char* origin() const { return origin_; }
const char* aux_data() const { return aux_data_; }

private:
v8::Persistent<v8::Context> context_;
const int group_id_;
const char* name_;
const char* origin_;
const char* aux_data_;
};

class Agent {
public:
explicit Agent(node::Environment* env);
Expand All @@ -57,6 +88,9 @@ class Agent {
// Stop and destroy io_
void Stop();

bool ContextCreated(const node::inspector::ContextInfo* info);
void ContextDestroyed(v8::Local<v8::Context> context);

bool IsStarted() { return !!client_; }

// IO thread started, and client connected
Expand Down Expand Up @@ -102,6 +136,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

bool enabled_;
std::string path_;
DebugOptions debug_options_;
Expand Down
Loading