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