• 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.h"
16 #include "node_url.h"
17 #include "util-inl.h"
18 #include "v8-inspector.h"
19 #include "v8-platform.h"
20 
21 #include "libplatform/libplatform.h"
22 
23 #ifdef __POSIX__
24 #include <pthread.h>
25 #include <climits>  // PTHREAD_STACK_MIN
26 #endif  // __POSIX__
27 
28 #include <algorithm>
29 #include <cstring>
30 #include <sstream>
31 #include <unordered_map>
32 #include <vector>
33 
34 namespace node {
35 namespace inspector {
36 namespace {
37 
38 using node::FatalError;
39 
40 using v8::Context;
41 using v8::Function;
42 using v8::Global;
43 using v8::HandleScope;
44 using v8::Isolate;
45 using v8::Local;
46 using v8::Message;
47 using v8::Object;
48 using v8::Task;
49 using v8::Value;
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)76 static void StartIoThreadWakeup(int signo) {
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 InspectorTimer {
330  public:
InspectorTimer(Environment * env,double interval_s,V8InspectorClient::TimerCallback callback,void * data)331   InspectorTimer(Environment* env,
332                  double interval_s,
333                  V8InspectorClient::TimerCallback callback,
334                  void* data) : env_(env),
335                                callback_(callback),
336                                data_(data) {
337     uv_timer_init(env->event_loop(), &timer_);
338     int64_t interval_ms = 1000 * interval_s;
339     uv_timer_start(&timer_, OnTimer, interval_ms, interval_ms);
340     timer_.data = this;
341   }
342 
343   InspectorTimer(const InspectorTimer&) = delete;
344 
Stop()345   void Stop() {
346     if (timer_.data == nullptr) return;
347 
348     timer_.data = nullptr;
349     uv_timer_stop(&timer_);
350     env_->CloseHandle(reinterpret_cast<uv_handle_t*>(&timer_), TimerClosedCb);
351   }
352 
env() const353   inline Environment* env() const { return env_; }
354 
355  private:
OnTimer(uv_timer_t * uvtimer)356   static void OnTimer(uv_timer_t* uvtimer) {
357     InspectorTimer* timer = node::ContainerOf(&InspectorTimer::timer_, uvtimer);
358     timer->callback_(timer->data_);
359   }
360 
TimerClosedCb(uv_handle_t * uvtimer)361   static void TimerClosedCb(uv_handle_t* uvtimer) {
362     std::unique_ptr<InspectorTimer> timer(
363         node::ContainerOf(&InspectorTimer::timer_,
364                           reinterpret_cast<uv_timer_t*>(uvtimer)));
365     // Unique_ptr goes out of scope here and pointer is deleted.
366   }
367 
368   ~InspectorTimer() = default;
369 
370   Environment* env_;
371   uv_timer_t timer_;
372   V8InspectorClient::TimerCallback callback_;
373   void* data_;
374 
375   friend std::unique_ptr<InspectorTimer>::deleter_type;
376 };
377 
378 class InspectorTimerHandle {
379  public:
InspectorTimerHandle(Environment * env,double interval_s,V8InspectorClient::TimerCallback callback,void * data)380   InspectorTimerHandle(Environment* env, double interval_s,
381                        V8InspectorClient::TimerCallback callback, void* data) {
382     timer_ = new InspectorTimer(env, interval_s, callback, data);
383 
384     env->AddCleanupHook(CleanupHook, this);
385   }
386 
387   InspectorTimerHandle(const InspectorTimerHandle&) = delete;
388 
~InspectorTimerHandle()389   ~InspectorTimerHandle() {
390     Stop();
391   }
392 
393  private:
Stop()394   void Stop() {
395     if (timer_ != nullptr) {
396       timer_->env()->RemoveCleanupHook(CleanupHook, this);
397       timer_->Stop();
398     }
399     timer_ = nullptr;
400   }
401 
CleanupHook(void * data)402   static void CleanupHook(void* data) {
403     static_cast<InspectorTimerHandle*>(data)->Stop();
404   }
405 
406   InspectorTimer* timer_;
407 };
408 
409 class SameThreadInspectorSession : public InspectorSession {
410  public:
SameThreadInspectorSession(int session_id,std::shared_ptr<NodeInspectorClient> client)411   SameThreadInspectorSession(
412       int session_id, std::shared_ptr<NodeInspectorClient> client)
413       : session_id_(session_id), client_(client) {}
414   ~SameThreadInspectorSession() override;
415   void Dispatch(const v8_inspector::StringView& message) override;
416 
417  private:
418   int session_id_;
419   std::weak_ptr<NodeInspectorClient> client_;
420 };
421 
NotifyClusterWorkersDebugEnabled(Environment * env)422 void NotifyClusterWorkersDebugEnabled(Environment* env) {
423   Isolate* isolate = env->isolate();
424   HandleScope handle_scope(isolate);
425   Local<Context> context = env->context();
426 
427   // Send message to enable debug in cluster workers
428   Local<Object> message = Object::New(isolate);
429   message->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cmd"),
430                FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")).Check();
431   ProcessEmit(env, "internalMessage", message);
432 }
433 
434 #ifdef _WIN32
IsFilePath(const std::string & path)435 bool IsFilePath(const std::string& path) {
436   // '\\'
437   if (path.length() > 2 && path[0] == '\\' && path[1] == '\\')
438     return true;
439   // '[A-Z]:[/\\]'
440   if (path.length() < 3)
441     return false;
442   if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
443     return path[1] == ':' && (path[2] == '/' || path[2] == '\\');
444   return false;
445 }
446 #else
IsFilePath(const std::string & path)447 bool IsFilePath(const std::string& path) {
448   return !path.empty() && path[0] == '/';
449 }
450 #endif  // __POSIX__
451 
452 }  // namespace
453 
454 class NodeInspectorClient : public V8InspectorClient {
455  public:
NodeInspectorClient(node::Environment * env,bool is_main)456   explicit NodeInspectorClient(node::Environment* env, bool is_main)
457       : env_(env), is_main_(is_main) {
458     client_ = V8Inspector::create(env->isolate(), this);
459     // TODO(bnoordhuis) Make name configurable from src/node.cc.
460     std::string name =
461         is_main_ ? GetHumanReadableProcessName() : GetWorkerLabel(env);
462     ContextInfo info(name);
463     info.is_default = true;
464     contextCreated(env->context(), info);
465   }
466 
runMessageLoopOnPause(int context_group_id)467   void runMessageLoopOnPause(int context_group_id) override {
468     waiting_for_resume_ = true;
469     runMessageLoop();
470   }
471 
waitForSessionsDisconnect()472   void waitForSessionsDisconnect() {
473     waiting_for_sessions_disconnect_ = true;
474     runMessageLoop();
475   }
476 
waitForFrontend()477   void waitForFrontend() {
478     waiting_for_frontend_ = true;
479     runMessageLoop();
480   }
481 
maxAsyncCallStackDepthChanged(int depth)482   void maxAsyncCallStackDepthChanged(int depth) override {
483     if (waiting_for_sessions_disconnect_) {
484       // V8 isolate is mostly done and is only letting Inspector protocol
485       // clients gather data.
486       return;
487     }
488     if (auto agent = env_->inspector_agent()) {
489       if (depth == 0) {
490         agent->DisableAsyncHook();
491       } else {
492         agent->EnableAsyncHook();
493       }
494     }
495   }
496 
contextCreated(Local<Context> context,const ContextInfo & info)497   void contextCreated(Local<Context> context, const ContextInfo& info) {
498     auto name_buffer = Utf8ToStringView(info.name);
499     auto origin_buffer = Utf8ToStringView(info.origin);
500     std::unique_ptr<StringBuffer> aux_data_buffer;
501 
502     v8_inspector::V8ContextInfo v8info(
503         context, CONTEXT_GROUP_ID, name_buffer->string());
504     v8info.origin = origin_buffer->string();
505 
506     if (info.is_default) {
507       aux_data_buffer = Utf8ToStringView("{\"isDefault\":true}");
508     } else {
509       aux_data_buffer = Utf8ToStringView("{\"isDefault\":false}");
510     }
511     v8info.auxData = aux_data_buffer->string();
512 
513     client_->contextCreated(v8info);
514   }
515 
contextDestroyed(Local<Context> context)516   void contextDestroyed(Local<Context> context) {
517     client_->contextDestroyed(context);
518   }
519 
quitMessageLoopOnPause()520   void quitMessageLoopOnPause() override {
521     waiting_for_resume_ = false;
522   }
523 
runIfWaitingForDebugger(int context_group_id)524   void runIfWaitingForDebugger(int context_group_id) override {
525     waiting_for_frontend_ = false;
526   }
527 
connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)528   int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,
529                       bool prevent_shutdown) {
530     int session_id = next_session_id_++;
531     channels_[session_id] = std::make_unique<ChannelImpl>(env_,
532                                                           client_,
533                                                           getWorkerManager(),
534                                                           std::move(delegate),
535                                                           getThreadHandle(),
536                                                           prevent_shutdown);
537     return session_id;
538   }
539 
disconnectFrontend(int session_id)540   void disconnectFrontend(int session_id) {
541     auto it = channels_.find(session_id);
542     if (it == channels_.end())
543       return;
544     bool retaining_context = it->second->retainingContext();
545     channels_.erase(it);
546     if (retaining_context) {
547       for (const auto& id_channel : channels_) {
548         if (id_channel.second->retainingContext())
549           return;
550       }
551       contextDestroyed(env_->context());
552     }
553     if (waiting_for_sessions_disconnect_ && !is_main_)
554       waiting_for_sessions_disconnect_ = false;
555   }
556 
dispatchMessageFromFrontend(int session_id,const StringView & message)557   void dispatchMessageFromFrontend(int session_id, const StringView& message) {
558     channels_[session_id]->dispatchProtocolMessage(message);
559   }
560 
ensureDefaultContextInGroup(int contextGroupId)561   Local<Context> ensureDefaultContextInGroup(int contextGroupId) override {
562     return env_->context();
563   }
564 
installAdditionalCommandLineAPI(Local<Context> context,Local<Object> target)565   void installAdditionalCommandLineAPI(Local<Context> context,
566                                        Local<Object> target) override {
567     Local<Function> installer = env_->inspector_console_extension_installer();
568     if (!installer.IsEmpty()) {
569       Local<Value> argv[] = {target};
570       // If there is an exception, proceed in JS land
571       USE(installer->Call(context, target, arraysize(argv), argv));
572     }
573   }
574 
ReportUncaughtException(Local<Value> error,Local<Message> message)575   void ReportUncaughtException(Local<Value> error, Local<Message> message) {
576     Isolate* isolate = env_->isolate();
577     Local<Context> context = env_->context();
578 
579     int script_id = message->GetScriptOrigin().ScriptID()->Value();
580 
581     Local<v8::StackTrace> stack_trace = message->GetStackTrace();
582 
583     if (!stack_trace.IsEmpty() && stack_trace->GetFrameCount() > 0 &&
584         script_id == stack_trace->GetFrame(isolate, 0)->GetScriptId()) {
585       script_id = 0;
586     }
587 
588     const uint8_t DETAILS[] = "Uncaught";
589 
590     client_->exceptionThrown(
591         context,
592         StringView(DETAILS, sizeof(DETAILS) - 1),
593         error,
594         ToProtocolString(isolate, message->Get())->string(),
595         ToProtocolString(isolate, message->GetScriptResourceName())->string(),
596         message->GetLineNumber(context).FromMaybe(0),
597         message->GetStartColumn(context).FromMaybe(0),
598         client_->createStackTrace(stack_trace),
599         script_id);
600   }
601 
startRepeatingTimer(double interval_s,TimerCallback callback,void * data)602   void startRepeatingTimer(double interval_s,
603                            TimerCallback callback,
604                            void* data) override {
605     timers_.emplace(std::piecewise_construct, std::make_tuple(data),
606                     std::make_tuple(env_, interval_s, callback,
607                                     data));
608   }
609 
cancelTimer(void * data)610   void cancelTimer(void* data) override {
611     timers_.erase(data);
612   }
613 
614   // Async stack traces instrumentation.
AsyncTaskScheduled(const StringView & task_name,void * task,bool recurring)615   void AsyncTaskScheduled(const StringView& task_name, void* task,
616                           bool recurring) {
617     client_->asyncTaskScheduled(task_name, task, recurring);
618   }
619 
AsyncTaskCanceled(void * task)620   void AsyncTaskCanceled(void* task) {
621     client_->asyncTaskCanceled(task);
622   }
623 
AsyncTaskStarted(void * task)624   void AsyncTaskStarted(void* task) {
625     client_->asyncTaskStarted(task);
626   }
627 
AsyncTaskFinished(void * task)628   void AsyncTaskFinished(void* task) {
629     client_->asyncTaskFinished(task);
630   }
631 
AllAsyncTasksCanceled()632   void AllAsyncTasksCanceled() {
633     client_->allAsyncTasksCanceled();
634   }
635 
schedulePauseOnNextStatement(const std::string & reason)636   void schedulePauseOnNextStatement(const std::string& reason) {
637     for (const auto& id_channel : channels_) {
638       id_channel.second->schedulePauseOnNextStatement(reason);
639     }
640   }
641 
hasConnectedSessions()642   bool hasConnectedSessions() {
643     for (const auto& id_channel : channels_) {
644       // Other sessions are "invisible" more most purposes
645       if (id_channel.second->preventShutdown())
646         return true;
647     }
648     return false;
649   }
650 
notifyWaitingForDisconnect()651   bool notifyWaitingForDisconnect() {
652     bool retaining_context = false;
653     for (const auto& id_channel : channels_) {
654       if (id_channel.second->notifyWaitingForDisconnect())
655         retaining_context = true;
656     }
657     return retaining_context;
658   }
659 
getThreadHandle()660   std::shared_ptr<MainThreadHandle> getThreadHandle() {
661     if (!interface_) {
662       interface_ = std::make_shared<MainThreadInterface>(
663           env_->inspector_agent());
664     }
665     return interface_->GetHandle();
666   }
667 
getWorkerManager()668   std::shared_ptr<WorkerManager> getWorkerManager() {
669     if (!is_main_) {
670       return nullptr;
671     }
672     if (worker_manager_ == nullptr) {
673       worker_manager_ =
674           std::make_shared<WorkerManager>(getThreadHandle());
675     }
676     return worker_manager_;
677   }
678 
IsActive()679   bool IsActive() {
680     return !channels_.empty();
681   }
682 
683  private:
shouldRunMessageLoop()684   bool shouldRunMessageLoop() {
685     if (waiting_for_frontend_)
686       return true;
687     if (waiting_for_sessions_disconnect_ || waiting_for_resume_) {
688       return hasConnectedSessions();
689     }
690     return false;
691   }
692 
runMessageLoop()693   void runMessageLoop() {
694     if (running_nested_loop_)
695       return;
696 
697     running_nested_loop_ = true;
698 
699     while (shouldRunMessageLoop()) {
700       if (interface_) interface_->WaitForFrontendEvent();
701       env_->RunAndClearInterrupts();
702     }
703     running_nested_loop_ = false;
704   }
705 
currentTimeMS()706   double currentTimeMS() override {
707     return env_->isolate_data()->platform()->CurrentClockTimeMillis();
708   }
709 
resourceNameToUrl(const StringView & resource_name_view)710   std::unique_ptr<StringBuffer> resourceNameToUrl(
711       const StringView& resource_name_view) override {
712     std::string resource_name =
713         protocol::StringUtil::StringViewToUtf8(resource_name_view);
714     if (!IsFilePath(resource_name))
715       return nullptr;
716     node::url::URL url = node::url::URL::FromFilePath(resource_name);
717     // TODO(ak239spb): replace this code with url.href().
718     // Refs: https://github.com/nodejs/node/issues/22610
719     return Utf8ToStringView(url.protocol() + "//" + url.path());
720   }
721 
722   node::Environment* env_;
723   bool is_main_;
724   bool running_nested_loop_ = false;
725   std::unique_ptr<V8Inspector> client_;
726   // Note: ~ChannelImpl may access timers_ so timers_ has to come first.
727   std::unordered_map<void*, InspectorTimerHandle> timers_;
728   std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_;
729   int next_session_id_ = 1;
730   bool waiting_for_resume_ = false;
731   bool waiting_for_frontend_ = false;
732   bool waiting_for_sessions_disconnect_ = false;
733   // Allows accessing Inspector from non-main threads
734   std::shared_ptr<MainThreadInterface> interface_;
735   std::shared_ptr<WorkerManager> worker_manager_;
736 };
737 
Agent(Environment * env)738 Agent::Agent(Environment* env)
739     : parent_env_(env),
740       debug_options_(env->options()->debug_options()),
741       host_port_(env->inspector_host_port()) {}
742 
~Agent()743 Agent::~Agent() {}
744 
Start(const std::string & path,const DebugOptions & options,std::shared_ptr<ExclusiveAccess<HostPort>> host_port,bool is_main)745 bool Agent::Start(const std::string& path,
746                   const DebugOptions& options,
747                   std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
748                   bool is_main) {
749   path_ = path;
750   debug_options_ = options;
751   CHECK_NOT_NULL(host_port);
752   host_port_ = host_port;
753 
754   client_ = std::make_shared<NodeInspectorClient>(parent_env_, is_main);
755   if (parent_env_->owns_inspector()) {
756     Mutex::ScopedLock lock(start_io_thread_async_mutex);
757     CHECK_EQ(start_io_thread_async_initialized.exchange(true), false);
758     CHECK_EQ(0, uv_async_init(parent_env_->event_loop(),
759                               &start_io_thread_async,
760                               StartIoThreadAsyncCallback));
761     uv_unref(reinterpret_cast<uv_handle_t*>(&start_io_thread_async));
762     start_io_thread_async.data = this;
763     // Ignore failure, SIGUSR1 won't work, but that should not block node start.
764     StartDebugSignalHandler();
765 
766     parent_env_->AddCleanupHook([](void* data) {
767       Environment* env = static_cast<Environment*>(data);
768 
769       {
770         Mutex::ScopedLock lock(start_io_thread_async_mutex);
771         start_io_thread_async.data = nullptr;
772       }
773 
774       // This is global, will never get freed
775       env->CloseHandle(&start_io_thread_async, [](uv_async_t*) {
776         CHECK(start_io_thread_async_initialized.exchange(false));
777       });
778     }, parent_env_);
779   }
780 
781   AtExit(parent_env_, [](void* env) {
782     Agent* agent = static_cast<Environment*>(env)->inspector_agent();
783     if (agent->IsActive()) {
784       agent->WaitForDisconnect();
785     }
786   }, parent_env_);
787 
788   bool wait_for_connect = options.wait_for_connect();
789   if (parent_handle_) {
790     wait_for_connect = parent_handle_->WaitForConnect();
791     parent_handle_->WorkerStarted(client_->getThreadHandle(), wait_for_connect);
792   } else if (!options.inspector_enabled || !StartIoThread()) {
793     return false;
794   }
795 
796   // Patch the debug options to implement waitForDebuggerOnStart for
797   // the NodeWorker.enable method.
798   if (wait_for_connect) {
799     CHECK(!parent_env_->has_serialized_options());
800     debug_options_.EnableBreakFirstLine();
801     parent_env_->options()->get_debug_options()->EnableBreakFirstLine();
802     client_->waitForFrontend();
803   }
804   return true;
805 }
806 
StartIoThread()807 bool Agent::StartIoThread() {
808   if (io_ != nullptr)
809     return true;
810 
811   CHECK_NOT_NULL(client_);
812 
813   io_ = InspectorIo::Start(client_->getThreadHandle(),
814                            path_,
815                            host_port_,
816                            debug_options_.inspect_publish_uid);
817   if (io_ == nullptr) {
818     return false;
819   }
820   NotifyClusterWorkersDebugEnabled(parent_env_);
821   return true;
822 }
823 
Stop()824 void Agent::Stop() {
825   io_.reset();
826 }
827 
Connect(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)828 std::unique_ptr<InspectorSession> Agent::Connect(
829     std::unique_ptr<InspectorSessionDelegate> delegate,
830     bool prevent_shutdown) {
831   CHECK_NOT_NULL(client_);
832   int session_id = client_->connectFrontend(std::move(delegate),
833                                             prevent_shutdown);
834   return std::unique_ptr<InspectorSession>(
835       new SameThreadInspectorSession(session_id, client_));
836 }
837 
ConnectToMainThread(std::unique_ptr<InspectorSessionDelegate> delegate,bool prevent_shutdown)838 std::unique_ptr<InspectorSession> Agent::ConnectToMainThread(
839     std::unique_ptr<InspectorSessionDelegate> delegate,
840     bool prevent_shutdown) {
841   CHECK_NOT_NULL(parent_handle_);
842   CHECK_NOT_NULL(client_);
843   auto thread_safe_delegate =
844       client_->getThreadHandle()->MakeDelegateThreadSafe(std::move(delegate));
845   return parent_handle_->Connect(std::move(thread_safe_delegate),
846                                  prevent_shutdown);
847 }
848 
WaitForDisconnect()849 void Agent::WaitForDisconnect() {
850   CHECK_NOT_NULL(client_);
851   bool is_worker = parent_handle_ != nullptr;
852   parent_handle_.reset();
853   if (client_->hasConnectedSessions() && !is_worker) {
854     fprintf(stderr, "Waiting for the debugger to disconnect...\n");
855     fflush(stderr);
856   }
857   if (!client_->notifyWaitingForDisconnect()) {
858     client_->contextDestroyed(parent_env_->context());
859   } else if (is_worker) {
860     client_->waitForSessionsDisconnect();
861   }
862   if (io_ != nullptr) {
863     io_->StopAcceptingNewConnections();
864     client_->waitForSessionsDisconnect();
865   }
866 }
867 
ReportUncaughtException(Local<Value> error,Local<Message> message)868 void Agent::ReportUncaughtException(Local<Value> error,
869                                     Local<Message> message) {
870   if (!IsListening())
871     return;
872   client_->ReportUncaughtException(error, message);
873   WaitForDisconnect();
874 }
875 
PauseOnNextJavascriptStatement(const std::string & reason)876 void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
877   client_->schedulePauseOnNextStatement(reason);
878 }
879 
RegisterAsyncHook(Isolate * isolate,Local<Function> enable_function,Local<Function> disable_function)880 void Agent::RegisterAsyncHook(Isolate* isolate,
881                               Local<Function> enable_function,
882                               Local<Function> disable_function) {
883   enable_async_hook_function_.Reset(isolate, enable_function);
884   disable_async_hook_function_.Reset(isolate, disable_function);
885   if (pending_enable_async_hook_) {
886     CHECK(!pending_disable_async_hook_);
887     pending_enable_async_hook_ = false;
888     EnableAsyncHook();
889   } else if (pending_disable_async_hook_) {
890     CHECK(!pending_enable_async_hook_);
891     pending_disable_async_hook_ = false;
892     DisableAsyncHook();
893   }
894 }
895 
EnableAsyncHook()896 void Agent::EnableAsyncHook() {
897   if (!enable_async_hook_function_.IsEmpty()) {
898     ToggleAsyncHook(parent_env_->isolate(), enable_async_hook_function_);
899   } else if (pending_disable_async_hook_) {
900     CHECK(!pending_enable_async_hook_);
901     pending_disable_async_hook_ = false;
902   } else {
903     pending_enable_async_hook_ = true;
904   }
905 }
906 
DisableAsyncHook()907 void Agent::DisableAsyncHook() {
908   if (!disable_async_hook_function_.IsEmpty()) {
909     ToggleAsyncHook(parent_env_->isolate(), disable_async_hook_function_);
910   } else if (pending_enable_async_hook_) {
911     CHECK(!pending_disable_async_hook_);
912     pending_enable_async_hook_ = false;
913   } else {
914     pending_disable_async_hook_ = true;
915   }
916 }
917 
ToggleAsyncHook(Isolate * isolate,const Global<Function> & fn)918 void Agent::ToggleAsyncHook(Isolate* isolate,
919                             const Global<Function>& fn) {
920   // Guard against running this during cleanup -- no async events will be
921   // emitted anyway at that point anymore, and calling into JS is not possible.
922   // This should probably not be something we're attempting in the first place,
923   // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039
924   if (!parent_env_->can_call_into_js()) return;
925   CHECK(parent_env_->has_run_bootstrapping_code());
926   HandleScope handle_scope(isolate);
927   CHECK(!fn.IsEmpty());
928   auto context = parent_env_->context();
929   v8::TryCatch try_catch(isolate);
930   USE(fn.Get(isolate)->Call(context, Undefined(isolate), 0, nullptr));
931   if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
932     PrintCaughtException(isolate, context, try_catch);
933     FatalError("\nnode::inspector::Agent::ToggleAsyncHook",
934                "Cannot toggle Inspector's AsyncHook, please report this.");
935   }
936 }
937 
AsyncTaskScheduled(const StringView & task_name,void * task,bool recurring)938 void Agent::AsyncTaskScheduled(const StringView& task_name, void* task,
939                                bool recurring) {
940   client_->AsyncTaskScheduled(task_name, task, recurring);
941 }
942 
AsyncTaskCanceled(void * task)943 void Agent::AsyncTaskCanceled(void* task) {
944   client_->AsyncTaskCanceled(task);
945 }
946 
AsyncTaskStarted(void * task)947 void Agent::AsyncTaskStarted(void* task) {
948   client_->AsyncTaskStarted(task);
949 }
950 
AsyncTaskFinished(void * task)951 void Agent::AsyncTaskFinished(void* task) {
952   client_->AsyncTaskFinished(task);
953 }
954 
AllAsyncTasksCanceled()955 void Agent::AllAsyncTasksCanceled() {
956   client_->AllAsyncTasksCanceled();
957 }
958 
RequestIoThreadStart()959 void Agent::RequestIoThreadStart() {
960   // We need to attempt to interrupt V8 flow (in case Node is running
961   // continuous JS code) and to wake up libuv thread (in case Node is waiting
962   // for IO events)
963   CHECK(start_io_thread_async_initialized);
964   uv_async_send(&start_io_thread_async);
965   parent_env_->RequestInterrupt([this](Environment*) {
966     StartIoThread();
967   });
968 
969   CHECK(start_io_thread_async_initialized);
970   uv_async_send(&start_io_thread_async);
971 }
972 
ContextCreated(Local<Context> context,const ContextInfo & info)973 void Agent::ContextCreated(Local<Context> context, const ContextInfo& info) {
974   if (client_ == nullptr)  // This happens for a main context
975     return;
976   client_->contextCreated(context, info);
977 }
978 
WillWaitForConnect()979 bool Agent::WillWaitForConnect() {
980   if (debug_options_.wait_for_connect()) return true;
981   if (parent_handle_)
982     return parent_handle_->WaitForConnect();
983   return false;
984 }
985 
IsActive()986 bool Agent::IsActive() {
987   if (client_ == nullptr)
988     return false;
989   return io_ != nullptr || client_->IsActive();
990 }
991 
SetParentHandle(std::unique_ptr<ParentInspectorHandle> parent_handle)992 void Agent::SetParentHandle(
993     std::unique_ptr<ParentInspectorHandle> parent_handle) {
994   parent_handle_ = std::move(parent_handle);
995 }
996 
GetParentHandle(int thread_id,const std::string & url)997 std::unique_ptr<ParentInspectorHandle> Agent::GetParentHandle(
998     int thread_id, const std::string& url) {
999   if (!parent_handle_) {
1000     return client_->getWorkerManager()->NewParentHandle(thread_id, url);
1001   } else {
1002     return parent_handle_->NewParentInspectorHandle(thread_id, url);
1003   }
1004 }
1005 
WaitForConnect()1006 void Agent::WaitForConnect() {
1007   CHECK_NOT_NULL(client_);
1008   client_->waitForFrontend();
1009 }
1010 
GetWorkerManager()1011 std::shared_ptr<WorkerManager> Agent::GetWorkerManager() {
1012   CHECK_NOT_NULL(client_);
1013   return client_->getWorkerManager();
1014 }
1015 
GetWsUrl() const1016 std::string Agent::GetWsUrl() const {
1017   if (io_ == nullptr)
1018     return "";
1019   return io_->GetWsUrl();
1020 }
1021 
~SameThreadInspectorSession()1022 SameThreadInspectorSession::~SameThreadInspectorSession() {
1023   auto client = client_.lock();
1024   if (client)
1025     client->disconnectFrontend(session_id_);
1026 }
1027 
Dispatch(const v8_inspector::StringView & message)1028 void SameThreadInspectorSession::Dispatch(
1029     const v8_inspector::StringView& message) {
1030   auto client = client_.lock();
1031   if (client)
1032     client->dispatchMessageFromFrontend(session_id_, message);
1033 }
1034 
1035 }  // namespace inspector
1036 }  // namespace node
1037