1 #include "base_object-inl.h"
2 #include "inspector_agent.h"
3 #include "inspector_io.h"
4 #include "memory_tracker-inl.h"
5 #include "util-inl.h"
6 #include "v8.h"
7 #include "v8-inspector.h"
8
9 #include <memory>
10
11 namespace node {
12 namespace inspector {
13 namespace {
14
15 using v8::Context;
16 using v8::Function;
17 using v8::FunctionCallbackInfo;
18 using v8::FunctionTemplate;
19 using v8::Global;
20 using v8::HandleScope;
21 using v8::Isolate;
22 using v8::Local;
23 using v8::MaybeLocal;
24 using v8::NewStringType;
25 using v8::Object;
26 using v8::String;
27 using v8::Uint32;
28 using v8::Value;
29
30 using v8_inspector::StringBuffer;
31 using v8_inspector::StringView;
32
ToProtocolString(Isolate * isolate,Local<Value> value)33 std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
34 Local<Value> value) {
35 TwoByteValue buffer(isolate, value);
36 return StringBuffer::create(StringView(*buffer, buffer.length()));
37 }
38
39 struct LocalConnection {
Connectnode::inspector::__anon4d0fa8450111::LocalConnection40 static std::unique_ptr<InspectorSession> Connect(
41 Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) {
42 return inspector->Connect(std::move(delegate), false);
43 }
44
GetClassNamenode::inspector::__anon4d0fa8450111::LocalConnection45 static Local<String> GetClassName(Environment* env) {
46 return FIXED_ONE_BYTE_STRING(env->isolate(), "Connection");
47 }
48 };
49
50 struct MainThreadConnection {
Connectnode::inspector::__anon4d0fa8450111::MainThreadConnection51 static std::unique_ptr<InspectorSession> Connect(
52 Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) {
53 return inspector->ConnectToMainThread(std::move(delegate), true);
54 }
55
GetClassNamenode::inspector::__anon4d0fa8450111::MainThreadConnection56 static Local<String> GetClassName(Environment* env) {
57 return FIXED_ONE_BYTE_STRING(env->isolate(), "MainThreadConnection");
58 }
59 };
60
61 template <typename ConnectionType>
62 class JSBindingsConnection : public AsyncWrap {
63 public:
64 class JSBindingsSessionDelegate : public InspectorSessionDelegate {
65 public:
JSBindingsSessionDelegate(Environment * env,JSBindingsConnection * connection)66 JSBindingsSessionDelegate(Environment* env,
67 JSBindingsConnection* connection)
68 : env_(env),
69 connection_(connection) {
70 }
71
SendMessageToFrontend(const v8_inspector::StringView & message)72 void SendMessageToFrontend(const v8_inspector::StringView& message)
73 override {
74 Isolate* isolate = env_->isolate();
75 HandleScope handle_scope(isolate);
76 Context::Scope context_scope(env_->context());
77 MaybeLocal<String> v8string =
78 String::NewFromTwoByte(isolate, message.characters16(),
79 NewStringType::kNormal, message.length());
80 Local<Value> argument = v8string.ToLocalChecked().As<Value>();
81 connection_->OnMessage(argument);
82 }
83
84 private:
85 Environment* env_;
86 BaseObjectPtr<JSBindingsConnection> connection_;
87 };
88
JSBindingsConnection(Environment * env,Local<Object> wrap,Local<Function> callback)89 JSBindingsConnection(Environment* env,
90 Local<Object> wrap,
91 Local<Function> callback)
92 : AsyncWrap(env, wrap, PROVIDER_INSPECTORJSBINDING),
93 callback_(env->isolate(), callback) {
94 Agent* inspector = env->inspector_agent();
95 session_ = ConnectionType::Connect(
96 inspector, std::make_unique<JSBindingsSessionDelegate>(env, this));
97 }
98
OnMessage(Local<Value> value)99 void OnMessage(Local<Value> value) {
100 MakeCallback(callback_.Get(env()->isolate()), 1, &value);
101 }
102
Bind(Environment * env,Local<Object> target)103 static void Bind(Environment* env, Local<Object> target) {
104 Local<String> class_name = ConnectionType::GetClassName(env);
105 Local<FunctionTemplate> tmpl =
106 env->NewFunctionTemplate(JSBindingsConnection::New);
107 tmpl->InstanceTemplate()->SetInternalFieldCount(
108 JSBindingsConnection::kInternalFieldCount);
109 tmpl->SetClassName(class_name);
110 tmpl->Inherit(AsyncWrap::GetConstructorTemplate(env));
111 env->SetProtoMethod(tmpl, "dispatch", JSBindingsConnection::Dispatch);
112 env->SetProtoMethod(tmpl, "disconnect", JSBindingsConnection::Disconnect);
113 target->Set(env->context(),
114 class_name,
115 tmpl->GetFunction(env->context()).ToLocalChecked())
116 .ToChecked();
117 }
118
New(const FunctionCallbackInfo<Value> & info)119 static void New(const FunctionCallbackInfo<Value>& info) {
120 Environment* env = Environment::GetCurrent(info);
121 CHECK(info[0]->IsFunction());
122 Local<Function> callback = info[0].As<Function>();
123 new JSBindingsConnection(env, info.This(), callback);
124 }
125
Disconnect()126 void Disconnect() {
127 session_.reset();
128 delete this;
129 }
130
Disconnect(const FunctionCallbackInfo<Value> & info)131 static void Disconnect(const FunctionCallbackInfo<Value>& info) {
132 JSBindingsConnection* session;
133 ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
134 session->Disconnect();
135 }
136
Dispatch(const FunctionCallbackInfo<Value> & info)137 static void Dispatch(const FunctionCallbackInfo<Value>& info) {
138 Environment* env = Environment::GetCurrent(info);
139 JSBindingsConnection* session;
140 ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
141 CHECK(info[0]->IsString());
142
143 if (session->session_) {
144 session->session_->Dispatch(
145 ToProtocolString(env->isolate(), info[0])->string());
146 }
147 }
148
MemoryInfo(MemoryTracker * tracker) const149 void MemoryInfo(MemoryTracker* tracker) const override {
150 tracker->TrackField("callback", callback_);
151 tracker->TrackFieldWithSize(
152 "session", sizeof(*session_), "InspectorSession");
153 }
154
155 SET_MEMORY_INFO_NAME(JSBindingsConnection)
156 SET_SELF_SIZE(JSBindingsConnection)
157
158 private:
159 std::unique_ptr<InspectorSession> session_;
160 Global<Function> callback_;
161 };
162
InspectorEnabled(Environment * env)163 static bool InspectorEnabled(Environment* env) {
164 Agent* agent = env->inspector_agent();
165 return agent->IsActive();
166 }
167
SetConsoleExtensionInstaller(const FunctionCallbackInfo<Value> & info)168 void SetConsoleExtensionInstaller(const FunctionCallbackInfo<Value>& info) {
169 auto env = Environment::GetCurrent(info);
170
171 CHECK_EQ(info.Length(), 1);
172 CHECK(info[0]->IsFunction());
173
174 env->set_inspector_console_extension_installer(info[0].As<Function>());
175 }
176
CallAndPauseOnStart(const FunctionCallbackInfo<v8::Value> & args)177 void CallAndPauseOnStart(const FunctionCallbackInfo<v8::Value>& args) {
178 Environment* env = Environment::GetCurrent(args);
179 CHECK_GT(args.Length(), 1);
180 CHECK(args[0]->IsFunction());
181 SlicedArguments call_args(args, /* start */ 2);
182 env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start");
183 v8::MaybeLocal<v8::Value> retval =
184 args[0].As<v8::Function>()->Call(env->context(), args[1],
185 call_args.length(), call_args.out());
186 if (!retval.IsEmpty()) {
187 args.GetReturnValue().Set(retval.ToLocalChecked());
188 }
189 }
190
InspectorConsoleCall(const FunctionCallbackInfo<Value> & info)191 void InspectorConsoleCall(const FunctionCallbackInfo<Value>& info) {
192 Environment* env = Environment::GetCurrent(info);
193 Isolate* isolate = env->isolate();
194 Local<Context> context = isolate->GetCurrentContext();
195 CHECK_GE(info.Length(), 2);
196 SlicedArguments call_args(info, /* start */ 2);
197 if (InspectorEnabled(env)) {
198 Local<Value> inspector_method = info[0];
199 CHECK(inspector_method->IsFunction());
200 if (!env->is_in_inspector_console_call()) {
201 env->set_is_in_inspector_console_call(true);
202 MaybeLocal<Value> ret =
203 inspector_method.As<Function>()->Call(context,
204 info.Holder(),
205 call_args.length(),
206 call_args.out());
207 env->set_is_in_inspector_console_call(false);
208 if (ret.IsEmpty())
209 return;
210 }
211 }
212
213 Local<Value> node_method = info[1];
214 CHECK(node_method->IsFunction());
215 node_method.As<Function>()->Call(context,
216 info.Holder(),
217 call_args.length(),
218 call_args.out()).FromMaybe(Local<Value>());
219 }
220
GetAsyncTask(int64_t asyncId)221 static void* GetAsyncTask(int64_t asyncId) {
222 // The inspector assumes that when other clients use its asyncTask* API,
223 // they use real pointers, or at least something aligned like real pointer.
224 // In general it means that our task_id should always be even.
225 //
226 // On 32bit platforms, the 64bit asyncId would get truncated when converted
227 // to a 32bit pointer. However, the javascript part will never enable
228 // the async_hook on 32bit platforms, therefore the truncation will never
229 // happen in practice.
230 return reinterpret_cast<void*>(asyncId << 1);
231 }
232
233 template <void (Agent::*asyncTaskFn)(void*)>
InvokeAsyncTaskFnWithId(const FunctionCallbackInfo<Value> & args)234 static void InvokeAsyncTaskFnWithId(const FunctionCallbackInfo<Value>& args) {
235 Environment* env = Environment::GetCurrent(args);
236 CHECK(args[0]->IsNumber());
237 int64_t task_id = args[0]->IntegerValue(env->context()).FromJust();
238 (env->inspector_agent()->*asyncTaskFn)(GetAsyncTask(task_id));
239 }
240
AsyncTaskScheduledWrapper(const FunctionCallbackInfo<Value> & args)241 static void AsyncTaskScheduledWrapper(const FunctionCallbackInfo<Value>& args) {
242 Environment* env = Environment::GetCurrent(args);
243
244 CHECK(args[0]->IsString());
245 Local<String> task_name = args[0].As<String>();
246 String::Value task_name_value(args.GetIsolate(), task_name);
247 StringView task_name_view(*task_name_value, task_name_value.length());
248
249 CHECK(args[1]->IsNumber());
250 int64_t task_id = args[1]->IntegerValue(env->context()).FromJust();
251 void* task = GetAsyncTask(task_id);
252
253 CHECK(args[2]->IsBoolean());
254 bool recurring = args[2]->BooleanValue(args.GetIsolate());
255
256 env->inspector_agent()->AsyncTaskScheduled(task_name_view, task, recurring);
257 }
258
RegisterAsyncHookWrapper(const FunctionCallbackInfo<Value> & args)259 static void RegisterAsyncHookWrapper(const FunctionCallbackInfo<Value>& args) {
260 Environment* env = Environment::GetCurrent(args);
261
262 CHECK(args[0]->IsFunction());
263 Local<Function> enable_function = args[0].As<Function>();
264 CHECK(args[1]->IsFunction());
265 Local<Function> disable_function = args[1].As<Function>();
266 env->inspector_agent()->RegisterAsyncHook(env->isolate(),
267 enable_function, disable_function);
268 }
269
IsEnabled(const FunctionCallbackInfo<Value> & args)270 void IsEnabled(const FunctionCallbackInfo<Value>& args) {
271 Environment* env = Environment::GetCurrent(args);
272 args.GetReturnValue().Set(InspectorEnabled(env));
273 }
274
Open(const FunctionCallbackInfo<Value> & args)275 void Open(const FunctionCallbackInfo<Value>& args) {
276 Environment* env = Environment::GetCurrent(args);
277 Agent* agent = env->inspector_agent();
278
279 if (args.Length() > 0 && args[0]->IsUint32()) {
280 uint32_t port = args[0].As<Uint32>()->Value();
281 ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port());
282 host_port->set_port(static_cast<int>(port));
283 }
284
285 if (args.Length() > 1 && args[1]->IsString()) {
286 Utf8Value host(env->isolate(), args[1].As<String>());
287 ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port());
288 host_port->set_host(*host);
289 }
290
291 agent->StartIoThread();
292 }
293
WaitForDebugger(const FunctionCallbackInfo<Value> & args)294 void WaitForDebugger(const FunctionCallbackInfo<Value>& args) {
295 Environment* env = Environment::GetCurrent(args);
296 Agent* agent = env->inspector_agent();
297 if (agent->IsActive())
298 agent->WaitForConnect();
299 args.GetReturnValue().Set(agent->IsActive());
300 }
301
Url(const FunctionCallbackInfo<Value> & args)302 void Url(const FunctionCallbackInfo<Value>& args) {
303 Environment* env = Environment::GetCurrent(args);
304 std::string url = env->inspector_agent()->GetWsUrl();
305 if (url.empty()) {
306 return;
307 }
308 args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str()));
309 }
310
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)311 void Initialize(Local<Object> target, Local<Value> unused,
312 Local<Context> context, void* priv) {
313 Environment* env = Environment::GetCurrent(context);
314
315 v8::Local<v8::Function> consoleCallFunc =
316 env->NewFunctionTemplate(InspectorConsoleCall, v8::Local<v8::Signature>(),
317 v8::ConstructorBehavior::kThrow,
318 v8::SideEffectType::kHasSideEffect)
319 ->GetFunction(context)
320 .ToLocalChecked();
321 auto name_string = FIXED_ONE_BYTE_STRING(env->isolate(), "consoleCall");
322 target->Set(context, name_string, consoleCallFunc).Check();
323 consoleCallFunc->SetName(name_string);
324
325 env->SetMethod(
326 target, "setConsoleExtensionInstaller", SetConsoleExtensionInstaller);
327 env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
328 env->SetMethod(target, "open", Open);
329 env->SetMethodNoSideEffect(target, "url", Url);
330 env->SetMethod(target, "waitForDebugger", WaitForDebugger);
331
332 env->SetMethod(target, "asyncTaskScheduled", AsyncTaskScheduledWrapper);
333 env->SetMethod(target, "asyncTaskCanceled",
334 InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>);
335 env->SetMethod(target, "asyncTaskStarted",
336 InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>);
337 env->SetMethod(target, "asyncTaskFinished",
338 InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>);
339
340 env->SetMethod(target, "registerAsyncHook", RegisterAsyncHookWrapper);
341 env->SetMethodNoSideEffect(target, "isEnabled", IsEnabled);
342
343 JSBindingsConnection<LocalConnection>::Bind(env, target);
344 JSBindingsConnection<MainThreadConnection>::Bind(env, target);
345 }
346
347 } // namespace
348 } // namespace inspector
349 } // namespace node
350
351 NODE_MODULE_CONTEXT_AWARE_INTERNAL(inspector,
352 node::inspector::Initialize)
353