Skip to content

Commit 9f12ee8

Browse files
committed
[WIP] src, inspector: support opted-in VM contexts
Based on work by Bradley Farias <bradley.meck@gmail.com> and Eugene Ostroukhov <eostroukhov@chromium.org>. TODO: V8's console is injected unconditionally, which may be not desirable. Fixes: nodejs#7593 Refs: nodejs#9272
1 parent ff88cf4 commit 9f12ee8

5 files changed

Lines changed: 192 additions & 12 deletions

File tree

lib/inspector.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
const EventEmitter = require('events');
44
const util = require('util');
5-
const { connect, open, url } = process.binding('inspector');
5+
const { isContext } = process.binding('contextify');
6+
const {
7+
connect,
8+
open,
9+
url,
10+
attachContext: _attachContext,
11+
detachContext: _detachContext
12+
} = process.binding('inspector');
613

714
if (!connect)
815
throw new Error('Inspector is not available');
@@ -86,9 +93,43 @@ class Session extends EventEmitter {
8693
}
8794
}
8895

96+
function checkSandbox(sandbox) {
97+
if (typeof sandbox !== 'object') {
98+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'sandbox',
99+
'object', sandbox);
100+
}
101+
if (!isContext(sandbox)) {
102+
throw new errors.TypeError('ERR_SANDBOX_NOT_CONTEXTIFIED');
103+
}
104+
}
105+
106+
let ctxIdx = 1;
107+
function attachContext(sandbox, {
108+
name = `vm Module Context ${ctxIdx}`,
109+
origin
110+
} = {}) {
111+
checkSandbox(sandbox);
112+
if (typeof name !== 'string') {
113+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options.name',
114+
'string', name);
115+
}
116+
if (origin !== undefined && typeof origin !== 'string') {
117+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options.origin',
118+
'string', origin);
119+
}
120+
_attachContext(sandbox, name, origin);
121+
}
122+
123+
function detachContext(sandbox) {
124+
checkSandbox(sandbox);
125+
_detachContext(sandbox);
126+
}
127+
89128
module.exports = {
90129
open: (port, host, wait) => open(port, host, !!wait),
91130
close: process._debugEnd,
92131
url: url,
93-
Session
132+
Session,
133+
attachContext,
134+
detachContext
94135
};

lib/internal/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function');
165165
E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object');
166166
E('ERR_NO_CRYPTO', 'Node.js is not compiled with OpenSSL crypto support');
167167
E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s');
168+
E('ERR_SANDBOX_NOT_CONTEXTIFIED',
169+
'Provided sandbox must have been converted to a context')
168170
E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound');
169171
E('ERR_SOCKET_BAD_TYPE',
170172
'Bad socket type specified. Valid types are: udp4, udp6');

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,10 @@
625625
'<(OBJ_PATH)<(OBJ_SEPARATOR)env.<(OBJ_SUFFIX)',
626626
'<(OBJ_PATH)<(OBJ_SEPARATOR)node.<(OBJ_SUFFIX)',
627627
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_buffer.<(OBJ_SUFFIX)',
628+
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_contextify.<(OBJ_SUFFIX)',
628629
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_i18n.<(OBJ_SUFFIX)',
629630
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_url.<(OBJ_SUFFIX)',
631+
'<(OBJ_PATH)<(OBJ_SEPARATOR)node_watchdog.<(OBJ_SUFFIX)',
630632
'<(OBJ_PATH)<(OBJ_SEPARATOR)util.<(OBJ_SUFFIX)',
631633
'<(OBJ_PATH)<(OBJ_SEPARATOR)string_bytes.<(OBJ_SUFFIX)',
632634
'<(OBJ_PATH)<(OBJ_SEPARATOR)string_search.<(OBJ_SUFFIX)',

src/inspector_agent.cc

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
#include "env.h"
55
#include "env-inl.h"
66
#include "node.h"
7+
#include "node_contextify.h"
78
#include "v8-inspector.h"
89
#include "v8-platform.h"
910
#include "util.h"
1011
#include "zlib.h"
1112

1213
#include "libplatform/libplatform.h"
1314

15+
#include <algorithm>
1416
#include <string.h>
1517
#include <vector>
1618

@@ -21,6 +23,9 @@
2123
namespace node {
2224
namespace inspector {
2325
namespace {
26+
27+
using node::contextify::ContextifyContext;
28+
2429
using v8::Context;
2530
using v8::External;
2631
using v8::Function;
@@ -43,6 +48,10 @@ using v8_inspector::V8Inspector;
4348
static uv_sem_t start_io_thread_semaphore;
4449
static uv_async_t start_io_thread_async;
4550

51+
// Used in NodeInspectorClient::currentTimeMS() below.
52+
const int NANOS_PER_MSEC = 1000000;
53+
const int CONTEXT_GROUP_ID = 1;
54+
4655
class StartIoTask : public v8::Task {
4756
public:
4857
explicit StartIoTask(Agent* agent) : agent(agent) {}
@@ -376,9 +385,61 @@ void CallAndPauseOnStart(
376385
}
377386
}
378387

379-
// Used in NodeInspectorClient::currentTimeMS() below.
380-
const int NANOS_PER_MSEC = 1000000;
381-
const int CONTEXT_GROUP_ID = 1;
388+
void AttachContext(const v8::FunctionCallbackInfo<v8::Value>& args) {
389+
Environment* env = Environment::GetCurrent(args);
390+
if (!args[0]->IsObject()) {
391+
env->ThrowTypeError("sandbox must be an object");
392+
return;
393+
}
394+
Local<Object> sandbox = args[0].As<Object>();
395+
ContextifyContext* contextify_context =
396+
ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
397+
if (contextify_context == nullptr) {
398+
return env->ThrowTypeError(
399+
"sandbox argument must have been converted to a context.");
400+
}
401+
402+
if (contextify_context->context().IsEmpty())
403+
return;
404+
405+
const char* name =
406+
args[1]->IsString() ?
407+
Utf8Value(env->isolate(), args[1]).out() :
408+
"vm Module Context";
409+
const char* origin =
410+
args[2]->IsString() ?
411+
Utf8Value(env->isolate(), args[2]).out() :
412+
nullptr;
413+
414+
// TODO(TimothyGu): Don't allow customizing group ID for now; not sure what
415+
// it's used for.
416+
int group_id = CONTEXT_GROUP_ID;
417+
418+
auto info = new node::inspector::ContextInfo(
419+
contextify_context->context(), group_id, name, origin,
420+
"{\"isDefault\":false}");
421+
env->inspector_agent()->ContextCreated(info);
422+
}
423+
424+
void DetachContext(const v8::FunctionCallbackInfo<v8::Value>& args) {
425+
Environment* env = Environment::GetCurrent(args);
426+
if (!args[0]->IsObject()) {
427+
env->ThrowTypeError("sandbox must be an object");
428+
return;
429+
}
430+
Local<Object> sandbox = args[0].As<Object>();
431+
ContextifyContext* contextify_context =
432+
ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
433+
if (contextify_context == nullptr) {
434+
return env->ThrowTypeError(
435+
"sandbox argument must have been converted to a context.");
436+
}
437+
438+
if (contextify_context->context().IsEmpty())
439+
return;
440+
441+
env->inspector_agent()->ContextDestroyed(contextify_context->context());
442+
}
382443

383444
class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
384445
public:
@@ -459,11 +520,24 @@ class NodeInspectorClient : public v8_inspector::V8InspectorClient {
459520
return uv_hrtime() * 1.0 / NANOS_PER_MSEC;
460521
}
461522

462-
void contextCreated(Local<Context> context, const std::string& name) {
463-
std::unique_ptr<StringBuffer> name_buffer = Utf8ToStringView(name);
464-
v8_inspector::V8ContextInfo info(context, CONTEXT_GROUP_ID,
465-
name_buffer->string());
466-
client_->contextCreated(info);
523+
void contextCreated(const node::inspector::ContextInfo* info) {
524+
std::unique_ptr<StringBuffer> name_buffer = Utf8ToStringView(info->name());
525+
v8_inspector::V8ContextInfo v8_info(info->context(env_->isolate()),
526+
info->group_id(),
527+
name_buffer->string());
528+
529+
std::unique_ptr<StringBuffer> origin_buffer;
530+
std::unique_ptr<StringBuffer> aux_data_buffer;
531+
if (info->origin() != nullptr) {
532+
origin_buffer = Utf8ToStringView(info->origin());
533+
v8_info.origin = origin_buffer->string();
534+
}
535+
if (info->aux_data() != nullptr) {
536+
aux_data_buffer = Utf8ToStringView(info->aux_data());
537+
v8_info.auxData = aux_data_buffer->string();
538+
}
539+
540+
client_->contextCreated(v8_info);
467541
}
468542

469543
void contextDestroyed(Local<Context> context) {
@@ -546,14 +620,36 @@ Agent::Agent(Environment* env) : parent_env_(env),
546620
Agent::~Agent() {
547621
}
548622

623+
void Agent::ContextCreated(const node::inspector::ContextInfo* info) {
624+
contexts_.push_back(info);
625+
client_->contextCreated(info);
626+
}
627+
628+
void Agent::ContextDestroyed(Local<Context> context) {
629+
auto it = std::find_if(
630+
contexts_.begin(), contexts_.end(),
631+
[&] (const node::inspector::ContextInfo*& info) {
632+
return info->context(parent_env_->isolate()) == context;
633+
});
634+
if (it == contexts_.end()) {
635+
return;
636+
}
637+
delete *it;
638+
contexts_.erase(it);
639+
client_->contextDestroyed(context);
640+
}
641+
549642
bool Agent::Start(v8::Platform* platform, const char* path,
550643
const DebugOptions& options) {
551644
path_ = path == nullptr ? "" : path;
552645
debug_options_ = options;
553646
client_ =
554647
std::unique_ptr<NodeInspectorClient>(
555648
new NodeInspectorClient(parent_env_, platform));
556-
client_->contextCreated(parent_env_->context(), "Node.js Main Context");
649+
ContextCreated(
650+
new node::inspector::ContextInfo(
651+
parent_env_->context(), CONTEXT_GROUP_ID, "Node.js Main Context",
652+
nullptr, "{\"isDefault\":true}"));
557653
platform_ = platform;
558654
CHECK_EQ(0, uv_async_init(uv_default_loop(),
559655
&start_io_thread_async,
@@ -627,7 +723,9 @@ bool Agent::IsConnected() {
627723

628724
void Agent::WaitForDisconnect() {
629725
CHECK_NE(client_, nullptr);
630-
client_->contextDestroyed(parent_env_->context());
726+
for (const node::inspector::ContextInfo*& info : contexts_) {
727+
ContextDestroyed(info->context(parent_env_->isolate()));
728+
}
631729
if (io_ != nullptr) {
632730
io_->WaitForDisconnect();
633731
}
@@ -713,6 +811,8 @@ void Agent::InitInspector(Local<Object> target, Local<Value> unused,
713811
Environment* env = Environment::GetCurrent(context);
714812
Agent* agent = env->inspector_agent();
715813
env->SetMethod(target, "consoleCall", InspectorConsoleCall);
814+
env->SetMethod(target, "attachContext", AttachContext);
815+
env->SetMethod(target, "detachContext", DetachContext);
716816
if (agent->debug_options_.wait_for_connect())
717817
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
718818
env->SetMethod(target, "connect", ConnectJSBindingsSession);

src/inspector_agent.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
#define SRC_INSPECTOR_AGENT_H_
33

44
#include <memory>
5+
#include <vector>
56

67
#include <stddef.h>
78

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

13+
#include "v8.h"
1214
#include "node_debug_options.h"
1315

1416
// Forward declaration to break recursive dependency chain with src/env.h.
@@ -46,6 +48,35 @@ class InspectorSessionDelegate {
4648
class InspectorIo;
4749
class NodeInspectorClient;
4850

51+
class ContextInfo {
52+
public:
53+
explicit ContextInfo(v8::Local<v8::Context> context, const int group_id,
54+
const char* name, const char* origin = nullptr,
55+
const char* aux_data = nullptr)
56+
: group_id_(group_id),
57+
name_(name),
58+
origin_(origin),
59+
aux_data_(aux_data) {
60+
context_.Reset(context->GetIsolate(), context);
61+
}
62+
63+
inline v8::Local<v8::Context> context(v8::Isolate* isolate) const {
64+
return context_.Get(isolate);
65+
}
66+
67+
int group_id() const { return group_id_; }
68+
const char* name() const { return name_; }
69+
const char* origin() const { return origin_; }
70+
const char* aux_data() const { return aux_data_; }
71+
72+
private:
73+
v8::Persistent<v8::Context> context_;
74+
const int group_id_;
75+
const char* name_;
76+
const char* origin_;
77+
const char* aux_data_;
78+
};
79+
4980
class Agent {
5081
public:
5182
explicit Agent(node::Environment* env);
@@ -57,6 +88,9 @@ class Agent {
5788
// Stop and destroy io_
5889
void Stop();
5990

91+
void ContextCreated(const node::inspector::ContextInfo* info);
92+
void ContextDestroyed(v8::Local<v8::Context> context);
93+
6094
bool IsStarted() { return !!client_; }
6195

6296
// IO thread started, and client connected
@@ -102,6 +136,7 @@ class Agent {
102136
std::unique_ptr<NodeInspectorClient> client_;
103137
std::unique_ptr<InspectorIo> io_;
104138
v8::Platform* platform_;
139+
std::vector<const node::inspector::ContextInfo*> contexts_;
105140
bool enabled_;
106141
std::string path_;
107142
DebugOptions debug_options_;

0 commit comments

Comments
 (0)