• 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 "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::__anon933ce84e0111::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::__anon933ce84e0111::LocalConnection45   static Local<String> GetClassName(Environment* env) {
46     return FIXED_ONE_BYTE_STRING(env->isolate(), "Connection");
47   }
48 };
49 
50 struct MainThreadConnection {
Connectnode::inspector::__anon933ce84e0111::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::__anon933ce84e0111::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<FunctionTemplate> tmpl =
105         env->NewFunctionTemplate(JSBindingsConnection::New);
106     tmpl->InstanceTemplate()->SetInternalFieldCount(
107         JSBindingsConnection::kInternalFieldCount);
108     tmpl->Inherit(AsyncWrap::GetConstructorTemplate(env));
109     env->SetProtoMethod(tmpl, "dispatch", JSBindingsConnection::Dispatch);
110     env->SetProtoMethod(tmpl, "disconnect", JSBindingsConnection::Disconnect);
111     env->SetConstructorFunction(
112         target,
113         ConnectionType::GetClassName(env),
114         tmpl);
115   }
116 
New(const FunctionCallbackInfo<Value> & info)117   static void New(const FunctionCallbackInfo<Value>& info) {
118     Environment* env = Environment::GetCurrent(info);
119     CHECK(info[0]->IsFunction());
120     Local<Function> callback = info[0].As<Function>();
121     new JSBindingsConnection(env, info.This(), callback);
122   }
123 
Disconnect()124   void Disconnect() {
125     session_.reset();
126     delete this;
127   }
128 
Disconnect(const FunctionCallbackInfo<Value> & info)129   static void Disconnect(const FunctionCallbackInfo<Value>& info) {
130     JSBindingsConnection* session;
131     ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
132     session->Disconnect();
133   }
134 
Dispatch(const FunctionCallbackInfo<Value> & info)135   static void Dispatch(const FunctionCallbackInfo<Value>& info) {
136     Environment* env = Environment::GetCurrent(info);
137     JSBindingsConnection* session;
138     ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
139     CHECK(info[0]->IsString());
140 
141     if (session->session_) {
142       session->session_->Dispatch(
143           ToProtocolString(env->isolate(), info[0])->string());
144     }
145   }
146 
MemoryInfo(MemoryTracker * tracker) const147   void MemoryInfo(MemoryTracker* tracker) const override {
148     tracker->TrackField("callback", callback_);
149     tracker->TrackFieldWithSize(
150         "session", sizeof(*session_), "InspectorSession");
151   }
152 
153   SET_MEMORY_INFO_NAME(JSBindingsConnection)
SET_SELF_SIZE(JSBindingsConnection)154   SET_SELF_SIZE(JSBindingsConnection)
155 
156   bool IsNotIndicativeOfMemoryLeakAtExit() const override {
157     return true;  // Binding connections emit events on their own.
158   }
159 
160  private:
161   std::unique_ptr<InspectorSession> session_;
162   Global<Function> callback_;
163 };
164 
InspectorEnabled(Environment * env)165 static bool InspectorEnabled(Environment* env) {
166   Agent* agent = env->inspector_agent();
167   return agent->IsActive();
168 }
169 
SetConsoleExtensionInstaller(const FunctionCallbackInfo<Value> & info)170 void SetConsoleExtensionInstaller(const FunctionCallbackInfo<Value>& info) {
171   auto env = Environment::GetCurrent(info);
172 
173   CHECK_EQ(info.Length(), 1);
174   CHECK(info[0]->IsFunction());
175 
176   env->set_inspector_console_extension_installer(info[0].As<Function>());
177 }
178 
CallAndPauseOnStart(const FunctionCallbackInfo<v8::Value> & args)179 void CallAndPauseOnStart(const FunctionCallbackInfo<v8::Value>& args) {
180   Environment* env = Environment::GetCurrent(args);
181   CHECK_GT(args.Length(), 1);
182   CHECK(args[0]->IsFunction());
183   SlicedArguments call_args(args, /* start */ 2);
184   env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start");
185   v8::MaybeLocal<v8::Value> retval =
186       args[0].As<v8::Function>()->Call(env->context(), args[1],
187                                        call_args.length(), call_args.out());
188   if (!retval.IsEmpty()) {
189     args.GetReturnValue().Set(retval.ToLocalChecked());
190   }
191 }
192 
InspectorConsoleCall(const FunctionCallbackInfo<Value> & info)193 void InspectorConsoleCall(const FunctionCallbackInfo<Value>& info) {
194   Environment* env = Environment::GetCurrent(info);
195   Isolate* isolate = env->isolate();
196   Local<Context> context = isolate->GetCurrentContext();
197   CHECK_GE(info.Length(), 2);
198   SlicedArguments call_args(info, /* start */ 2);
199   if (InspectorEnabled(env)) {
200     Local<Value> inspector_method = info[0];
201     CHECK(inspector_method->IsFunction());
202     if (!env->is_in_inspector_console_call()) {
203       env->set_is_in_inspector_console_call(true);
204       MaybeLocal<Value> ret =
205           inspector_method.As<Function>()->Call(context,
206                                                 info.Holder(),
207                                                 call_args.length(),
208                                                 call_args.out());
209       env->set_is_in_inspector_console_call(false);
210       if (ret.IsEmpty())
211         return;
212     }
213   }
214 
215   Local<Value> node_method = info[1];
216   CHECK(node_method->IsFunction());
217   node_method.As<Function>()->Call(context,
218                                    info.Holder(),
219                                    call_args.length(),
220                                    call_args.out()).FromMaybe(Local<Value>());
221 }
222 
GetAsyncTask(int64_t asyncId)223 static void* GetAsyncTask(int64_t asyncId) {
224   // The inspector assumes that when other clients use its asyncTask* API,
225   // they use real pointers, or at least something aligned like real pointer.
226   // In general it means that our task_id should always be even.
227   //
228   // On 32bit platforms, the 64bit asyncId would get truncated when converted
229   // to a 32bit pointer. However, the javascript part will never enable
230   // the async_hook on 32bit platforms, therefore the truncation will never
231   // happen in practice.
232   return reinterpret_cast<void*>(asyncId << 1);
233 }
234 
235 template <void (Agent::*asyncTaskFn)(void*)>
InvokeAsyncTaskFnWithId(const FunctionCallbackInfo<Value> & args)236 static void InvokeAsyncTaskFnWithId(const FunctionCallbackInfo<Value>& args) {
237   Environment* env = Environment::GetCurrent(args);
238   CHECK(args[0]->IsNumber());
239   int64_t task_id = args[0]->IntegerValue(env->context()).FromJust();
240   (env->inspector_agent()->*asyncTaskFn)(GetAsyncTask(task_id));
241 }
242 
AsyncTaskScheduledWrapper(const FunctionCallbackInfo<Value> & args)243 static void AsyncTaskScheduledWrapper(const FunctionCallbackInfo<Value>& args) {
244   Environment* env = Environment::GetCurrent(args);
245 
246   CHECK(args[0]->IsString());
247   Local<String> task_name = args[0].As<String>();
248   String::Value task_name_value(args.GetIsolate(), task_name);
249   StringView task_name_view(*task_name_value, task_name_value.length());
250 
251   CHECK(args[1]->IsNumber());
252   int64_t task_id = args[1]->IntegerValue(env->context()).FromJust();
253   void* task = GetAsyncTask(task_id);
254 
255   CHECK(args[2]->IsBoolean());
256   bool recurring = args[2]->BooleanValue(args.GetIsolate());
257 
258   env->inspector_agent()->AsyncTaskScheduled(task_name_view, task, recurring);
259 }
260 
RegisterAsyncHookWrapper(const FunctionCallbackInfo<Value> & args)261 static void RegisterAsyncHookWrapper(const FunctionCallbackInfo<Value>& args) {
262   Environment* env = Environment::GetCurrent(args);
263 
264   CHECK(args[0]->IsFunction());
265   Local<Function> enable_function = args[0].As<Function>();
266   CHECK(args[1]->IsFunction());
267   Local<Function> disable_function = args[1].As<Function>();
268   env->inspector_agent()->RegisterAsyncHook(env->isolate(),
269     enable_function, disable_function);
270 }
271 
IsEnabled(const FunctionCallbackInfo<Value> & args)272 void IsEnabled(const FunctionCallbackInfo<Value>& args) {
273   Environment* env = Environment::GetCurrent(args);
274   args.GetReturnValue().Set(InspectorEnabled(env));
275 }
276 
Open(const FunctionCallbackInfo<Value> & args)277 void Open(const FunctionCallbackInfo<Value>& args) {
278   Environment* env = Environment::GetCurrent(args);
279   Agent* agent = env->inspector_agent();
280 
281   if (args.Length() > 0 && args[0]->IsUint32()) {
282     uint32_t port = args[0].As<Uint32>()->Value();
283     ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port());
284     host_port->set_port(static_cast<int>(port));
285   }
286 
287   if (args.Length() > 1 && args[1]->IsString()) {
288     Utf8Value host(env->isolate(), args[1].As<String>());
289     ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port());
290     host_port->set_host(*host);
291   }
292 
293   agent->StartIoThread();
294 }
295 
WaitForDebugger(const FunctionCallbackInfo<Value> & args)296 void WaitForDebugger(const FunctionCallbackInfo<Value>& args) {
297   Environment* env = Environment::GetCurrent(args);
298   Agent* agent = env->inspector_agent();
299   if (agent->IsActive())
300     agent->WaitForConnect();
301   args.GetReturnValue().Set(agent->IsActive());
302 }
303 
Url(const FunctionCallbackInfo<Value> & args)304 void Url(const FunctionCallbackInfo<Value>& args) {
305   Environment* env = Environment::GetCurrent(args);
306   std::string url = env->inspector_agent()->GetWsUrl();
307   if (url.empty()) {
308     return;
309   }
310   args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str()));
311 }
312 
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)313 void Initialize(Local<Object> target, Local<Value> unused,
314                 Local<Context> context, void* priv) {
315   Environment* env = Environment::GetCurrent(context);
316 
317   v8::Local<v8::Function> consoleCallFunc =
318       env->NewFunctionTemplate(InspectorConsoleCall, v8::Local<v8::Signature>(),
319                                v8::ConstructorBehavior::kThrow,
320                                v8::SideEffectType::kHasSideEffect)
321           ->GetFunction(context)
322           .ToLocalChecked();
323   auto name_string = FIXED_ONE_BYTE_STRING(env->isolate(), "consoleCall");
324   target->Set(context, name_string, consoleCallFunc).Check();
325   consoleCallFunc->SetName(name_string);
326 
327   env->SetMethod(
328       target, "setConsoleExtensionInstaller", SetConsoleExtensionInstaller);
329   env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
330   env->SetMethod(target, "open", Open);
331   env->SetMethodNoSideEffect(target, "url", Url);
332   env->SetMethod(target, "waitForDebugger", WaitForDebugger);
333 
334   env->SetMethod(target, "asyncTaskScheduled", AsyncTaskScheduledWrapper);
335   env->SetMethod(target, "asyncTaskCanceled",
336       InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>);
337   env->SetMethod(target, "asyncTaskStarted",
338       InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>);
339   env->SetMethod(target, "asyncTaskFinished",
340       InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>);
341 
342   env->SetMethod(target, "registerAsyncHook", RegisterAsyncHookWrapper);
343   env->SetMethodNoSideEffect(target, "isEnabled", IsEnabled);
344 
345   JSBindingsConnection<LocalConnection>::Bind(env, target);
346   JSBindingsConnection<MainThreadConnection>::Bind(env, target);
347 }
348 
349 }  // namespace
350 }  // namespace inspector
351 }  // namespace node
352 
353 NODE_MODULE_CONTEXT_AWARE_INTERNAL(inspector,
354                                   node::inspector::Initialize)
355