• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "inspector_agent.h"
2 
3 #include "env-inl.h"
4 #include "inspector/main_thread_interface.h"
5 #include "inspector/node_string.h"
6 #include "inspector/runtime_agent.h"
7 #include "inspector/tracing_agent.h"
8 #include "inspector/worker_agent.h"
9 #include "inspector/worker_inspector.h"
10 #include "inspector_io.h"
11 #include "node/inspector/protocol/Protocol.h"
12 #include "node_errors.h"
13 #include "node_internals.h"
14 #include "node_options-inl.h"
15 #include "node_process-inl.h"
16 #include "node_url.h"
17 #include "util-inl.h"
18 #include "timer_wrap.h"
19 #include "v8-inspector.h"
20 #include "v8-platform.h"
21 
22 #include "libplatform/libplatform.h"
23 
24 #ifdef __POSIX__
25 #include <pthread.h>
26 #include <climits>  // PTHREAD_STACK_MIN
27 #endif  // __POSIX__
28 
29 #include <algorithm>
30 #include <cstring>
31 #include <sstream>
32 #include <unordered_map>
33 #include <vector>
34 
35 namespace node {
36 namespace inspector {
37 namespace {
38 
39 using node::FatalError;
40 
41 using v8::Context;
42 using v8::Function;
43 using v8::HandleScope;
44 using v8::Isolate;
45 using v8::Local;
46 using v8::Message;
47 using v8::Object;
48 using v8::Value;
49 
50 using v8_inspector::StringBuffer;
51 using v8_inspector::StringView;
52 using v8_inspector::V8Inspector;
53 using v8_inspector::V8InspectorClient;
54 
55 static uv_sem_t start_io_thread_semaphore;
56 static uv_async_t start_io_thread_async;
57 // This is just an additional check to make sure start_io_thread_async
58 // is not accidentally re-used or used when uninitialized.
59 static std::atomic_bool start_io_thread_async_initialized { false };
60 // Protects the Agent* stored in start_io_thread_async.data.
61 static Mutex start_io_thread_async_mutex;
62 
ToProtocolString(Isolate * isolate,Local<Value> value)63 std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
64                                                Local<Value> value) {
65   TwoByteValue buffer(isolate, value);
66   return StringBuffer::create(StringView(*buffer, buffer.length()));
67 }
68 
69 // Called on the main thread.
StartIoThreadAsyncCallback(uv_async_t * handle)70 void StartIoThreadAsyncCallback(uv_async_t* handle) {
71   static_cast<Agent*>(handle->data)->StartIoThread();
72 }
73 
74 
75 #ifdef __POSIX__
StartIoThreadWakeup(int signo,siginfo_t * info,void * ucontext)76 static void StartIoThreadWakeup(int signo, siginfo_t* info, void* ucontext) {
77   uv_sem_post(&start_io_thread_semaphore);
78 }
79 
StartIoThreadMain(void * unused)80 inline void* StartIoThreadMain(void* unused) {
81   for (;;) {
82     uv_sem_wait(&start_io_thread_semaphore);
83     Mutex::ScopedLock lock(start_io_thread_async_mutex);
84 
85     CHECK(start_io_thread_async_initialized);
86     Agent* agent = static_cast<Agent*>(start_io_thread_async.data);
87     if (agent != nullptr)
88       agent->RequestIoThreadStart();
89   }
90   return nullptr;
91 }
92 
StartDebugSignalHandler()93 static int StartDebugSignalHandler() {
94   // Start a watchdog thread for calling v8::Debug::DebugBreak() because
95   // it's not safe to call directly from the signal handler, it can
96   // deadlock with the thread it interrupts.
97   CHECK_EQ(0, uv_sem_init(&start_io_thread_semaphore, 0));
98   pthread_attr_t attr;
99   CHECK_EQ(0, pthread_attr_init(&attr));
100 #if defined(PTHREAD_STACK_MIN) && !defined(__FreeBSD__)
101   // PTHREAD_STACK_MIN is 2 KB with musl libc, which is too small to safely
102   // receive signals. PTHREAD_STACK_MIN + MINSIGSTKSZ is 8 KB on arm64, which
103   // is the musl architecture with the biggest MINSIGSTKSZ so let's use that
104   // as a lower bound and let's quadruple it just in case. The goal is to avoid
105   // creating a big 2 or 4 MB address space gap (problematic on 32 bits
106   // because of fragmentation), not squeeze out every last byte.
107   // Omitted on FreeBSD because it doesn't seem to like small stacks.
108   const size_t stack_size = std::max(static_cast<size_t>(4 * 8192),
109                                      static_cast<size_t>(PTHREAD_STACK_MIN));
110   CHECK_EQ(0, pthread_attr_setstacksize(&attr, stack_size));
111 #endif  // defined(PTHREAD_STACK_MIN) && !defined(__FreeBSD__)
112   CHECK_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
113   sigset_t sigmask;
114   // Mask all signals.
115   sigfillset(&sigmask);
116   sigset_t savemask;
117   CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &savemask));
118   sigmask = savemask;
119   pthread_t thread;
120   const int err = pthread_create(&thread, &attr,
121                                  StartIoThreadMain, nullptr);
122   // Restore original mask
123   CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
124   CHECK_EQ(0, pthread_attr_destroy(&attr));
125   if (err != 0) {
126     fprintf(stderr, "node[%u]: pthread_create: %s\n",
127             uv_os_getpid(), strerror(err));
128     fflush(stderr);
129     // Leave SIGUSR1 blocked.  We don't install a signal handler,
130     // receiving the signal would terminate the process.
131     return -err;
132   }
133   RegisterSignalHandler(SIGUSR1, StartIoThreadWakeup);
134   // Unblock SIGUSR1.  A pending SIGUSR1 signal will now be delivered.
135   sigemptyset(&sigmask);
136   sigaddset(&sigmask, SIGUSR1);
137   CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sigmask, nullptr));
138   return 0;
139 }
140 #endif  // __POSIX__
141 
142 
143 #ifdef _WIN32
StartIoThreadProc(void * arg)144 DWORD WINAPI StartIoThreadProc(void* arg) {
145   Mutex::ScopedLock lock(start_io_thread_async_mutex);
146   CHECK(start_io_thread_async_initialized);
147   Agent* agent = static_cast<Agent*>(start_io_thread_async.data);
148   if (agent != nullptr)
149     agent->RequestIoThreadStart();
150   return 0;
151 }
152 
GetDebugSignalHandlerMappingName(DWORD pid,wchar_t * buf,size_t buf_len)153 static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t* buf,
154                                             size_t buf_len) {
155   return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid);
156 }
157 
StartDebugSignalHandler()158 static int StartDebugSignalHandler() {
159   wchar_t mapping_name[32];
160   HANDLE mapping_handle;
161   DWORD pid;
162   LPTHREAD_START_ROUTINE* handler;
163 
164   pid = uv_os_getpid();
165 
166   if (GetDebugSignalHandlerMappingName(pid,
167                                        mapping_name,
168                                        arraysize(mapping_name)) < 0) {
169     return -1;
170   }
171 
172   mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE,
173                                       nullptr,
174                                       PAGE_READWRITE,
175                                       0,
176                                       sizeof *handler,
177                                       mapping_name);
178   if (mapping_handle == nullptr) {
179     return -1;
180   }
181 
182   handler = reinterpret_cast<LPTHREAD_START_ROUTINE*>(
183       MapViewOfFile(mapping_handle,
184                     FILE_MAP_ALL_ACCESS,
185                     0,
186                     0,
187                     sizeof *handler));
188   if (handler == nullptr) {
189     CloseHandle(mapping_handle);
190     return -1;
191   }
192 
193   *handler = StartIoThreadProc;
194 
195   UnmapViewOfFile(static_cast<void*>(handler));
196 
197   return 0;
198 }
199 #endif  // _WIN32
200 
201 
202 const int CONTEXT_GROUP_ID = 1;
203 
GetWorkerLabel(node::Environment * env)204 std::string GetWorkerLabel(node::Environment* env) {
205   std::ostringstream result;
206   result << "Worker[" << env->thread_id() << "]";
207   return result.str();
208 }
209 
210 class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
211                           public protocol::FrontendChannel {
212  public:
ChannelImpl(Environment * env,const std::unique_ptr<V8Inspector> & inspector,std::shared_ptr<WorkerManager> worker_manager,std::unique_ptr<InspectorSessionDelegate> delegate,std::shared_ptr<MainThreadHandle> main_thread_,bool prevent_shutdown)213   explicit ChannelImpl(Environment* env,
214                        const std::unique_ptr<V8Inspector>& inspector,
215                        std::shared_ptr<WorkerManager> worker_manager,
216                        std::unique_ptr<InspectorSessionDelegate> delegate,
217                        std::shared_ptr<MainThreadHandle> main_thread_,
218                        bool prevent_shutdown)
219       : delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown),
220         retaining_context_(false) {
221     session_ = inspector->connect(CONTEXT_GROUP_ID, this, StringView());
222     node_dispatcher_ = std::make_unique<protocol::UberDispatcher>(this);
223     tracing_agent_ =
224         std::make_unique<protocol::TracingAgent>(env, main_thread_);
225     tracing_agent_->Wire(node_dispatcher_.get());
226     if (worker_manager) {
227       worker_agent_ = std::make_unique<protocol::WorkerAgent>(worker_manager);
228       worker_agent_->Wire(node_dispatcher_.get());
229     }
230     runtime_agent_ = std::make_unique<protocol::RuntimeAgent>();
231     runtime_agent_->Wire(node_dispatcher_.get());
232   }
233 
~ChannelImpl()234   ~ChannelImpl() override {
235     tracing_agent_->disable();
236     tracing_agent_.reset();  // Dispose before the dispatchers
237     if (worker_agent_) {
238       worker_agent_->disable();
239       worker_agent_.reset();  // Dispose before the dispatchers
240     }
241     runtime_agent_->disable();
242     runtime_agent_.reset();  // Dispose before the dispatchers
243   }
244 
dispatchProtocolMessage(const StringView & message)245   void dispatchProtocolMessage(const StringView& message) {
246     std::string raw_message = protocol::StringUtil::StringViewToUtf8(message);
247     std::unique_ptr<protocol::DictionaryValue> value =
248         protocol::DictionaryValue::cast(protocol::StringUtil::parseMessage(
249             raw_message, false));
250     int call_id;
251     std::string method;
252     node_dispatcher_->parseCommand(value.get(), &call_id, &method);
253     if (v8_inspector::V8InspectorSession::canDispatchMethod(
254             Utf8ToStringView(method)->string())) {
255       session_->dispatchProtocolMessage(message);
256     } else {
257       node_dispatcher_->dispatch(call_id, method, std::move(value),
258                                  raw_message);
259     }
260   }
261 
schedulePauseOnNextStatement(const std::string & reason)262   void schedulePauseOnNextStatement(const std::string& reason) {
263     std::unique_ptr<StringBuffer> buffer = Utf8ToStringView(reason);
264     session_->schedulePauseOnNextStatement(buffer->string(), buffer->string());
265   }
266 
preventShutdown()267   bool preventShutdown() {
268     return prevent_shutdown_;
269   }
270 
notifyWaitingForDisconnect()271   bool notifyWaitingForDisconnect() {
272     retaining_context_ = runtime_agent_->notifyWaitingForDisconnect();
273     return retaining_context_;
274   }
275 
retainingContext()276   bool retainingContext() {
277     return retaining_context_;
278   }
279 
280  private:
sendResponse(int callId,std::unique_ptr<v8_inspector::StringBuffer> message)281   void sendResponse(
282       int callId,
283       std::unique_ptr<v8_inspector::StringBuffer> message) override {
284     sendMessageToFrontend(message->string());
285   }
286 
sendNotification(std::unique_ptr<v8_inspector::StringBuffer> message)287   void sendNotification(
288       std::unique_ptr<v8_inspector::StringBuffer> message) override {
289     sendMessageToFrontend(message->string());
290   }
291 
flushProtocolNotifications()292   void flushProtocolNotifications() override { }
293 
sendMessageToFrontend(const StringView & message)294   void sendMessageToFrontend(const StringView& message) {
295     delegate_->SendMessageToFrontend(message);
296   }
297 
sendMessageToFrontend(const std::string & message)298   void sendMessageToFrontend(const std::string& message) {
299     sendMessageToFrontend(Utf8ToStringView(message)->string());
300   }
301 
302   using Serializable = protocol::Serializable;
303 
sendProtocolResponse(int callId,std::unique_ptr<Serializable> message)304   void sendProtocolResponse(int callId,
305                             std::unique_ptr<Serializable> message) override {
306     sendMessageToFrontend(message->serializeToJSON());
307   }
sendProtocolNotification(std::unique_ptr<Serializable> message)308   void sendProtocolNotification(
309       std::unique_ptr<Serializable> message) override {
310     sendMessageToFrontend(message->serializeToJSON());
311   }
312 
fallThrough(int callId,const std::string & method,const std::string & message)313   void fallThrough(int callId,
314                    const std::string& method,
315                    const std::string& message) override {
316     DCHECK(false);
317   }
318 
319   std::unique_ptr<protocol::RuntimeAgent> runtime_agent_;
320   std::unique_ptr<protocol::TracingAgent> tracing_agent_;
321   std::unique_ptr<protocol::WorkerAgent> worker_agent_;
322   std::unique_ptr<InspectorSessionDelegate> delegate_;
323   std::unique_ptr<v8_inspector::V8InspectorSession> session_;
324   std::unique_ptr<protocol::UberDispatcher> node_dispatcher_;
325   bool prevent_shutdown_;
326   bool retaining_context_;
327 };
328 
329 class SameThreadInspectorSession : public InspectorSession {
330  public:
SameThreadInspectorSession(int session_id,std::shared_ptr<NodeInspectorClient> client)331   SameThreadInspectorSession(
332       int session_id, std::shared_ptr<NodeInspectorClient> client)
333       : session_id_(session_id), client_(client) {}
334   ~SameThreadInspectorSession() override;
335   void Dispatch(const v8_inspector::StringView& message) override;
336 
337  private:
338   int session_id_;
339   std::weak_ptr<NodeInspectorClient> client_;
340 };
341 
NotifyClusterWorkersDebugEnabled(Environment * env)342 void NotifyClusterWorkersDebugEnabled(Environment* env) {
343   Isolate* isolate = env->isolate();
344   HandleScope handle_scope(isolate);
345   Local<Context> context = env->context();
346 
347   // Send message to enable debug in cluster workers
348   Local<Object> message = Object::New(isolate);
349   message->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cmd"),
350                FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")).Check();
351   ProcessEmit(env, "internalMessage", message);
352 }
353 
354 #ifdef _WIN32
IsFilePath(const std::string & path)355 bool IsFilePath(const std::string& path) {
356   // '\\'
357   if (path.length() > 2 && path[0] == '\\' && path[1] == '\\')
358     return true;
359   // '[A-Z]:[/\\]'
360   if (path.length() < 3)
361     return false;
362   if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
363     return path[1] == ':' && (path[2] == '/' || path[2] == '\\');
364   return false;
365 }
366 #else
IsFilePath(const std::string & path)367 bool IsFilePath(const std::string& path) {
368   return !path.empty() && path[0] == '/';
369 }
370 #endif  // __POSIX__
371 
372 }  // namespace
373 
374 class NodeInspectorClient : public V8InspectorClient {
375  public:
NodeInspectorClient(node::Environment * env,bool is_main)376   explicit NodeInspectorClient(node::Environment* env, bool is_main)
377       : env_(env), is_main_(is_main) {
378     client_ = V8Inspector::create(env->isolate(), this);
379     // TODO(bnoordhuis) Make name configurable from src/node.cc.
380     std::string name =
381         is_main_ ? GetHumanReadableProcessName() : GetWorkerLabel(env);
382     ContextInfo info(name);
383     info.is_default = true;
384     contextCreated(env->context(), info);
385   }
386 
runMessageLoopOnPause(int context_group_id)387   void runMessageLoopOnPause(int context_group_id) override {
388     waiting_for_resume_ = true;
389     runMessageLoop();
390   }
391 
waitForSessionsDisconnect()392   void waitForSessionsDisconnect() {
393     waiting_for_sessions_disconnect_ = true;
394     runMessageLoop();
395   }
396 
waitForFrontend()397   void waitForFrontend() {
398     waiting_for_frontend_ = true;
399     runMessageLoop();
400   }
401 
maxAsyncCallStackDepthChanged(int depth)402   void maxAsyncCallStackDepthChanged(int depth) override {
403     if (waiting_for_sessions_disconnect_) {
404       // V8 isolate is mostly done and is only letting Inspector protocol
405       // clients gather data.
406       return;
407     }
408     if (auto agent = env_->inspector_agent()) {
409       if (depth == 0) {
410         agent->DisableAsyncHook();
411       } else {
412         agent->EnableAsyncHook();
413       }
414     }
415   }
416 
contextCreated(Local<Context> context,const ContextInfo & info)417   void contextCreated(Local<Context> context, const ContextInfo& info) {
418     auto name_buffer = Utf8ToStringView(info.name);
419     auto origin_buffer = Utf8ToStringView(info.origin);
420     std::unique_ptr<StringBuffer> aux_data_buffer;
421 
422     v8_inspector::V8ContextInfo v8info(
423         context, CONTEXT_GROUP_ID, name_buffer->string());
424     v8info.origin = origin_buffer->string();
425 
426     if (info.is_default) {
427       aux_data_buffer = Utf8ToStringView("{\"isDefault\":true}");
428     } else {
429       aux_data_buffer = Utf8ToStringView("{\"isDefault\":false}");
430     }
431     v8info.auxData = aux_data_buffer->string();
432 
433     client_->contextCreated(v8info);
434   }
435 
contextDestroyed(Local<Context> context)436   void contextDestroyed(Local<Context> context) {
437     client_->contextDestroyed(context);
438   }
439 
quitMessageLoopOnPause()440   void quitMessageLoopOnPause() override {
441     waiting_for_resume_ = false;
442   }
443 
runIfWaitingForDebugger(int context_group_id)444   void runIfWaitingForDebugger(int context_group_id) override {
445     waiting_for_frontend_ = false;
446   }
447 
connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)448   int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,
449                       bool prevent_shutdown) {
450     int session_id = next_session_id_++;
451     channels_[session_id] = std::make_unique<ChannelImpl>(env_,
452                                                           client_,
453                                                           getWorkerManager(),
454                                                           std::move(delegate),
455                                                           getThreadHandle(),
456                                                           prevent_shutdown);
457     return session_id;
458   }
459 
disconnectFrontend(int session_id)460   void disconnectFrontend(int session_id) {
461     auto it = channels_.find(session_id);
462     if (it == channels_.end())
463       return;
464     bool retaining_context = it->second->retainingContext();
465     channels_.erase(it);
466     if (retaining_context) {
467       for (const auto& id_channel : channels_) {
468         if (id_channel.second->retainingContext())
469           return;
470       }
471       contextDestroyed(env_->context());
472     }
473     if (waiting_for_sessions_disconnect_ && !is_main_)
474       waiting_for_sessions_disconnect_ = false;
475   }
476 
dispatchMessageFromFrontend(int session_id,const StringView & message)477   void dispatchMessageFromFrontend(int session_id, const StringView& message) {
478     channels_[session_id]->dispatchProtocolMessage(message);
479   }
480 
ensureDefaultContextInGroup(int contextGroupId)481   Local<Context> ensureDefaultContextInGroup(int contextGroupId) override {
482     return env_->context();
483   }
484 
installAdditionalCommandLineAPI(Local<Context> context,Local<Object> target)485   void installAdditionalCommandLineAPI(Local<Context> context,
486                                        Local<Object> target) override {
487     Local<Function> installer = env_->inspector_console_extension_installer();
488     if (!installer.IsEmpty()) {
489       Local<Value> argv[] = {target};
490       // If there is an exception, proceed in JS land
491       USE(installer->Call(context, target, arraysize(argv), argv));
492     }
493   }
494 
ReportUncaughtException(Local<Value> error,Local<Message> message)495   void ReportUncaughtException(Local<Value> error, Local<Message> message) {
496     Isolate* isolate = env_->isolate();
497     Local<Context> context = env_->context();
498 
499     int script_id = message->GetScriptOrigin().ScriptID()->Value();
500 
501     Local<v8::StackTrace> stack_trace = message->GetStackTrace();
502 
503     if (!stack_trace.IsEmpty() && stack_trace->GetFrameCount() > 0 &&
504         script_id == stack_trace->GetFrame(isolate, 0)->GetScriptId()) {
505       script_id = 0;
506     }
507 
508     const uint8_t DETAILS[] = "Uncaught";
509 
510     client_->exceptionThrown(
511         context,
512         StringView(DETAILS, sizeof(DETAILS) - 1),
513         error,
514         ToProtocolString(isolate, message->Get())->string(),
515         ToProtocolString(isolate, message->GetScriptResourceName())->string(),
516         message->GetLineNumber(context).FromMaybe(0),
517         message->GetStartColumn(context).FromMaybe(0),
518         client_->createStackTrace(stack_trace),
519         script_id);
520   }
521 
startRepeatingTimer(double interval_s,TimerCallback callback,void * data)522   void startRepeatingTimer(double interval_s,
523                            TimerCallback callback,
524                            void* data) override {
525     auto result =
526         timers_.emplace(std::piecewise_construct, std::make_tuple(data),
527                         std::make_tuple(env_, [=]() { callback(data); }));
528     CHECK(result.second);
529     uint64_t interval = static_cast<uint64_t>(1000 * interval_s);
530     result.first->second.Update(interval, interval);
531   }
532 
cancelTimer(void * data)533   void cancelTimer(void* data) override {
534     timers_.erase(data);
535   }
536 
537   // Async stack traces instrumentation.
AsyncTaskScheduled(const StringView & task_name,void * task,bool recurring)538   void AsyncTaskScheduled(const StringView& task_name, void* task,
539                           bool recurring) {
540     client_->asyncTaskScheduled(task_name, task, recurring);
541   }
542 
AsyncTaskCanceled(void * task)543   void AsyncTaskCanceled(void* task) {
544     client_->asyncTaskCanceled(task);
545   }
546 
AsyncTaskStarted(void * task)547   void AsyncTaskStarted(void* task) {
548     client_->asyncTaskStarted(task);
549   }
550 
AsyncTaskFinished(void * task)551   void AsyncTaskFinished(void* task) {
552     client_->asyncTaskFinished(task);
553   }
554 
AllAsyncTasksCanceled()555   void AllAsyncTasksCanceled() {
556     client_->allAsyncTasksCanceled();
557   }
558 
schedulePauseOnNextStatement(const std::string & reason)559   void schedulePauseOnNextStatement(const std::string& reason) {
560     for (const auto& id_channel : channels_) {
561       id_channel.second->schedulePauseOnNextStatement(reason);
562     }
563   }
564 
hasConnectedSessions()565   bool hasConnectedSessions() {
566     for (const auto& id_channel : channels_) {
567       // Other sessions are "invisible" more most purposes
568       if (id_channel.second->preventShutdown())
569         return true;
570     }
571     return false;
572   }
573 
notifyWaitingForDisconnect()574   bool notifyWaitingForDisconnect() {
575     bool retaining_context = false;
576     for (const auto& id_channel : channels_) {
577       if (id_channel.second->notifyWaitingForDisconnect())
578         retaining_context = true;
579     }
580     return retaining_context;
581   }
582 
getThreadHandle()583   std::shared_ptr<MainThreadHandle> getThreadHandle() {
584     if (!interface_) {
585       interface_ = std::make_shared<MainThreadInterface>(
586           env_->inspector_agent());
587     }
588     return interface_->GetHandle();
589   }
590 
getWorkerManager()591   std::shared_ptr<WorkerManager> getWorkerManager() {
592     if (!is_main_) {
593       return nullptr;
594     }
595     if (worker_manager_ == nullptr) {
596       worker_manager_ =
597           std::make_shared<WorkerManager>(getThreadHandle());
598     }
599     return worker_manager_;
600   }
601 
IsActive()602   bool IsActive() {
603     return !channels_.empty();
604   }
605 
606  private:
shouldRunMessageLoop()607   bool shouldRunMessageLoop() {
608     if (waiting_for_frontend_)
609       return true;
610     if (waiting_for_sessions_disconnect_ || waiting_for_resume_) {
611       return hasConnectedSessions();
612     }
613     return false;
614   }
615 
runMessageLoop()616   void runMessageLoop() {
617     if (running_nested_loop_)
618       return;
619 
620     running_nested_loop_ = true;
621 
622     while (shouldRunMessageLoop()) {
623       if (interface_) interface_->WaitForFrontendEvent();
624       env_->RunAndClearInterrupts();
625     }
626     running_nested_loop_ = false;
627   }
628 
currentTimeMS()629   double currentTimeMS() override {
630     return env_->isolate_data()->platform()->CurrentClockTimeMillis();
631   }
632 
resourceNameToUrl(const StringView & resource_name_view)633   std::unique_ptr<StringBuffer> resourceNameToUrl(
634       const StringView& resource_name_view) override {
635     std::string resource_name =
636         protocol::StringUtil::StringViewToUtf8(resource_name_view);
637     if (!IsFilePath(resource_name))
638       return nullptr;
639     node::url::URL url = node::url::URL::FromFilePath(resource_name);
640     // TODO(ak239spb): replace this code with url.href().
641     // Refs: https://github.com/nodejs/node/issues/22610
642     return Utf8ToStringView(url.protocol() + "//" + url.path());
643   }
644 
645   node::Environment* env_;
646   bool is_main_;
647   bool running_nested_loop_ = false;
648   std::unique_ptr<V8Inspector> client_;
649   // Note: ~ChannelImpl may access timers_ so timers_ has to come first.
650   std::unordered_map<void*, TimerWrapHandle> timers_;
651   std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_;
652   int next_session_id_ = 1;
653   bool waiting_for_resume_ = false;
654   bool waiting_for_frontend_ = false;
655   bool waiting_for_sessions_disconnect_ = false;
656   // Allows accessing Inspector from non-main threads
657   std::shared_ptr<MainThreadInterface> interface_;
658   std::shared_ptr<WorkerManager> worker_manager_;
659 };
660 
Agent(Environment * env)661 Agent::Agent(Environment* env)
662     : parent_env_(env),
663       debug_options_(env->options()->debug_options()),
664       host_port_(env->inspector_host_port()) {}
665 
~Agent()666 Agent::~Agent() {}
667 
Start(const std::string & path,const DebugOptions & options,std::shared_ptr<ExclusiveAccess<HostPort>> host_port,bool is_main)668 bool Agent::Start(const std::string& path,
669                   const DebugOptions& options,
670                   std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
671                   bool is_main) {
672   path_ = path;
673   debug_options_ = options;
674   CHECK_NOT_NULL(host_port);
675   host_port_ = host_port;
676 
677   client_ = std::make_shared<NodeInspectorClient>(parent_env_, is_main);
678   if (parent_env_->owns_inspector()) {
679     Mutex::ScopedLock lock(start_io_thread_async_mutex);
680     CHECK_EQ(start_io_thread_async_initialized.exchange(true), false);
681     CHECK_EQ(0, uv_async_init(parent_env_->event_loop(),
682                               &start_io_thread_async,
683                               StartIoThreadAsyncCallback));
684     uv_unref(reinterpret_cast<uv_handle_t*>(&start_io_thread_async));
685     start_io_thread_async.data = this;
686     // Ignore failure, SIGUSR1 won't work, but that should not block node start.
687     StartDebugSignalHandler();
688 
689     parent_env_->AddCleanupHook([](void* data) {
690       Environment* env = static_cast<Environment*>(data);
691 
692       {
693         Mutex::ScopedLock lock(start_io_thread_async_mutex);
694         start_io_thread_async.data = nullptr;
695       }
696 
697       // This is global, will never get freed
698       env->CloseHandle(&start_io_thread_async, [](uv_async_t*) {
699         CHECK(start_io_thread_async_initialized.exchange(false));
700       });
701     }, parent_env_);
702   }
703 
704   AtExit(parent_env_, [](void* env) {
705     Agent* agent = static_cast<Environment*>(env)->inspector_agent();
706     if (agent->IsActive()) {
707       agent->WaitForDisconnect();
708     }
709   }, parent_env_);
710 
711   bool wait_for_connect = options.wait_for_connect();
712   if (parent_handle_) {
713     wait_for_connect = parent_handle_->WaitForConnect();
714     parent_handle_->WorkerStarted(client_->getThreadHandle(), wait_for_connect);
715   } else if (!options.inspector_enabled || !StartIoThread()) {
716     return false;
717   }
718 
719   // Patch the debug options to implement waitForDebuggerOnStart for
720   // the NodeWorker.enable method.
721   if (wait_for_connect) {
722     CHECK(!parent_env_->has_serialized_options());
723     debug_options_.EnableBreakFirstLine();
724     parent_env_->options()->get_debug_options()->EnableBreakFirstLine();
725     client_->waitForFrontend();
726   }
727   return true;
728 }
729 
StartIoThread()730 bool Agent::StartIoThread() {
731   if (io_ != nullptr)
732     return true;
733 
734   CHECK_NOT_NULL(client_);
735 
736   io_ = InspectorIo::Start(client_->getThreadHandle(),
737                            path_,
738                            host_port_,
739                            debug_options_.inspect_publish_uid);
740   if (io_ == nullptr) {
741     return false;
742   }
743   NotifyClusterWorkersDebugEnabled(parent_env_);
744   return true;
745 }
746 
Stop()747 void Agent::Stop() {
748   io_.reset();
749 }
750 
Connect(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)751 std::unique_ptr<InspectorSession> Agent::Connect(
752     std::unique_ptr<InspectorSessionDelegate> delegate,
753     bool prevent_shutdown) {
754   CHECK_NOT_NULL(client_);
755   int session_id = client_->connectFrontend(std::move(delegate),
756                                             prevent_shutdown);
757   return std::unique_ptr<InspectorSession>(
758       new SameThreadInspectorSession(session_id, client_));
759 }
760 
ConnectToMainThread(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)761 std::unique_ptr<InspectorSession> Agent::ConnectToMainThread(
762     std::unique_ptr<InspectorSessionDelegate> delegate,
763     bool prevent_shutdown) {
764   CHECK_NOT_NULL(parent_handle_);
765   CHECK_NOT_NULL(client_);
766   auto thread_safe_delegate =
767       client_->getThreadHandle()->MakeDelegateThreadSafe(std::move(delegate));
768   return parent_handle_->Connect(std::move(thread_safe_delegate),
769                                  prevent_shutdown);
770 }
771 
WaitForDisconnect()772 void Agent::WaitForDisconnect() {
773   CHECK_NOT_NULL(client_);
774   bool is_worker = parent_handle_ != nullptr;
775   parent_handle_.reset();
776   if (client_->hasConnectedSessions() && !is_worker) {
777     fprintf(stderr, "Waiting for the debugger to disconnect...\n");
778     fflush(stderr);
779   }
780   if (!client_->notifyWaitingForDisconnect()) {
781     client_->contextDestroyed(parent_env_->context());
782   } else if (is_worker) {
783     client_->waitForSessionsDisconnect();
784   }
785   if (io_ != nullptr) {
786     io_->StopAcceptingNewConnections();
787     client_->waitForSessionsDisconnect();
788   }
789 }
790 
ReportUncaughtException(Local<Value> error,Local<Message> message)791 void Agent::ReportUncaughtException(Local<Value> error,
792                                     Local<Message> message) {
793   if (!IsListening())
794     return;
795   client_->ReportUncaughtException(error, message);
796   WaitForDisconnect();
797 }
798 
PauseOnNextJavascriptStatement(const std::string & reason)799 void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
800   client_->schedulePauseOnNextStatement(reason);
801 }
802 
RegisterAsyncHook(Isolate * isolate,Local<Function> enable_function,Local<Function> disable_function)803 void Agent::RegisterAsyncHook(Isolate* isolate,
804                               Local<Function> enable_function,
805                               Local<Function> disable_function) {
806   parent_env_->set_inspector_enable_async_hooks(enable_function);
807   parent_env_->set_inspector_disable_async_hooks(disable_function);
808   if (pending_enable_async_hook_) {
809     CHECK(!pending_disable_async_hook_);
810     pending_enable_async_hook_ = false;
811     EnableAsyncHook();
812   } else if (pending_disable_async_hook_) {
813     CHECK(!pending_enable_async_hook_);
814     pending_disable_async_hook_ = false;
815     DisableAsyncHook();
816   }
817 }
818 
EnableAsyncHook()819 void Agent::EnableAsyncHook() {
820   HandleScope scope(parent_env_->isolate());
821   Local<Function> enable = parent_env_->inspector_enable_async_hooks();
822   if (!enable.IsEmpty()) {
823     ToggleAsyncHook(parent_env_->isolate(), enable);
824   } else if (pending_disable_async_hook_) {
825     CHECK(!pending_enable_async_hook_);
826     pending_disable_async_hook_ = false;
827   } else {
828     pending_enable_async_hook_ = true;
829   }
830 }
831 
DisableAsyncHook()832 void Agent::DisableAsyncHook() {
833   HandleScope scope(parent_env_->isolate());
834   Local<Function> disable = parent_env_->inspector_enable_async_hooks();
835   if (!disable.IsEmpty()) {
836     ToggleAsyncHook(parent_env_->isolate(), disable);
837   } else if (pending_enable_async_hook_) {
838     CHECK(!pending_disable_async_hook_);
839     pending_enable_async_hook_ = false;
840   } else {
841     pending_disable_async_hook_ = true;
842   }
843 }
844 
ToggleAsyncHook(Isolate * isolate,Local<Function> fn)845 void Agent::ToggleAsyncHook(Isolate* isolate, Local<Function> fn) {
846   // Guard against running this during cleanup -- no async events will be
847   // emitted anyway at that point anymore, and calling into JS is not possible.
848   // This should probably not be something we're attempting in the first place,
849   // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
850   if (!parent_env_->can_call_into_js()) return;
851   CHECK(parent_env_->has_run_bootstrapping_code());
852   HandleScope handle_scope(isolate);
853   CHECK(!fn.IsEmpty());
854   auto context = parent_env_->context();
855   v8::TryCatch try_catch(isolate);
856   USE(fn->Call(context, Undefined(isolate), 0, nullptr));
857   if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
858     PrintCaughtException(isolate, context, try_catch);
859     FatalError("\nnode::inspector::Agent::ToggleAsyncHook",
860                "Cannot toggle Inspector's AsyncHook, please report this.");
861   }
862 }
863 
AsyncTaskScheduled(const StringView & task_name,void * task,bool recurring)864 void Agent::AsyncTaskScheduled(const StringView& task_name, void* task,
865                                bool recurring) {
866   client_->AsyncTaskScheduled(task_name, task, recurring);
867 }
868 
AsyncTaskCanceled(void * task)869 void Agent::AsyncTaskCanceled(void* task) {
870   client_->AsyncTaskCanceled(task);
871 }
872 
AsyncTaskStarted(void * task)873 void Agent::AsyncTaskStarted(void* task) {
874   client_->AsyncTaskStarted(task);
875 }
876 
AsyncTaskFinished(void * task)877 void Agent::AsyncTaskFinished(void* task) {
878   client_->AsyncTaskFinished(task);
879 }
880 
AllAsyncTasksCanceled()881 void Agent::AllAsyncTasksCanceled() {
882   client_->AllAsyncTasksCanceled();
883 }
884 
RequestIoThreadStart()885 void Agent::RequestIoThreadStart() {
886   // We need to attempt to interrupt V8 flow (in case Node is running
887   // continuous JS code) and to wake up libuv thread (in case Node is waiting
888   // for IO events)
889   CHECK(start_io_thread_async_initialized);
890   uv_async_send(&start_io_thread_async);
891   parent_env_->RequestInterrupt([this](Environment*) {
892     StartIoThread();
893   });
894 
895   CHECK(start_io_thread_async_initialized);
896   uv_async_send(&start_io_thread_async);
897 }
898 
ContextCreated(Local<Context> context,const ContextInfo & info)899 void Agent::ContextCreated(Local<Context> context, const ContextInfo& info) {
900   if (client_ == nullptr)  // This happens for a main context
901     return;
902   client_->contextCreated(context, info);
903 }
904 
IsActive()905 bool Agent::IsActive() {
906   if (client_ == nullptr)
907     return false;
908   return io_ != nullptr || client_->IsActive();
909 }
910 
SetParentHandle(std::unique_ptr<ParentInspectorHandle> parent_handle)911 void Agent::SetParentHandle(
912     std::unique_ptr<ParentInspectorHandle> parent_handle) {
913   parent_handle_ = std::move(parent_handle);
914 }
915 
GetParentHandle(uint64_t thread_id,const std::string & url)916 std::unique_ptr<ParentInspectorHandle> Agent::GetParentHandle(
917     uint64_t thread_id, const std::string& url) {
918   if (!parent_handle_) {
919     return client_->getWorkerManager()->NewParentHandle(thread_id, url);
920   } else {
921     return parent_handle_->NewParentInspectorHandle(thread_id, url);
922   }
923 }
924 
WaitForConnect()925 void Agent::WaitForConnect() {
926   CHECK_NOT_NULL(client_);
927   client_->waitForFrontend();
928 }
929 
GetWorkerManager()930 std::shared_ptr<WorkerManager> Agent::GetWorkerManager() {
931   CHECK_NOT_NULL(client_);
932   return client_->getWorkerManager();
933 }
934 
GetWsUrl() const935 std::string Agent::GetWsUrl() const {
936   if (io_ == nullptr)
937     return "";
938   return io_->GetWsUrl();
939 }
940 
~SameThreadInspectorSession()941 SameThreadInspectorSession::~SameThreadInspectorSession() {
942   auto client = client_.lock();
943   if (client)
944     client->disconnectFrontend(session_id_);
945 }
946 
Dispatch(const v8_inspector::StringView & message)947 void SameThreadInspectorSession::Dispatch(
948     const v8_inspector::StringView& message) {
949   auto client = client_.lock();
950   if (client)
951     client->dispatchMessageFromFrontend(session_id_, message);
952 }
953 
954 }  // namespace inspector
955 }  // namespace node
956