Node.js  v8.x
Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine
inspector_io.cc
Go to the documentation of this file.
1 #include "inspector_io.h"
2 
4 #include "env.h"
5 #include "env-inl.h"
6 #include "node.h"
7 #include "node_crypto.h"
8 #include "node_mutex.h"
9 #include "v8-inspector.h"
10 #include "util.h"
11 #include "zlib.h"
12 
13 #include <sstream>
14 #include <unicode/unistr.h>
15 
16 #include <string.h>
17 #include <vector>
18 
19 
20 namespace node {
21 namespace inspector {
22 namespace {
23 using AsyncAndAgent = std::pair<uv_async_t, Agent*>;
24 using v8_inspector::StringBuffer;
25 using v8_inspector::StringView;
26 
27 template<typename Transport>
28 using TransportAndIo = std::pair<Transport*, InspectorIo*>;
29 
30 std::string GetProcessTitle() {
31  char title[2048];
32  int err = uv_get_process_title(title, sizeof(title));
33  if (err == 0) {
34  return title;
35  } else {
36  // Title is too long, or could not be retrieved.
37  return "Node.js";
38  }
39 }
40 
41 std::string ScriptPath(uv_loop_t* loop, const std::string& script_name) {
42  std::string script_path;
43 
44  if (!script_name.empty()) {
45  uv_fs_t req;
46  req.ptr = nullptr;
47  if (0 == uv_fs_realpath(loop, &req, script_name.c_str(), nullptr)) {
48  CHECK_NE(req.ptr, nullptr);
49  script_path = std::string(static_cast<char*>(req.ptr));
50  }
51  uv_fs_req_cleanup(&req);
52  }
53 
54  return script_path;
55 }
56 
57 // UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
58 // Used ver 4 - with numbers
59 std::string GenerateID() {
60  uint16_t buffer[8];
61  CHECK(crypto::EntropySource(reinterpret_cast<unsigned char*>(buffer),
62  sizeof(buffer)));
63 
64  char uuid[256];
65  snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
66  buffer[0], // time_low
67  buffer[1], // time_mid
68  buffer[2], // time_low
69  (buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version
70  (buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low
71  buffer[5], // node
72  buffer[6],
73  buffer[7]);
74  return uuid;
75 }
76 
77 std::string StringViewToUtf8(const StringView& view) {
78  if (view.is8Bit()) {
79  return std::string(reinterpret_cast<const char*>(view.characters8()),
80  view.length());
81  }
82  const uint16_t* source = view.characters16();
83  const UChar* unicodeSource = reinterpret_cast<const UChar*>(source);
84  static_assert(sizeof(*source) == sizeof(*unicodeSource),
85  "sizeof(*source) == sizeof(*unicodeSource)");
86 
87  size_t result_length = view.length() * sizeof(*source);
88  std::string result(result_length, '\0');
89  UnicodeString utf16(unicodeSource, view.length());
90  // ICU components for std::string compatibility are not enabled in build...
91  bool done = false;
92  while (!done) {
93  CheckedArrayByteSink sink(&result[0], result_length);
94  utf16.toUTF8(sink);
95  result_length = sink.NumberOfBytesAppended();
96  result.resize(result_length);
97  done = !sink.Overflowed();
98  }
99  return result;
100 }
101 
102 void HandleSyncCloseCb(uv_handle_t* handle) {
103  *static_cast<bool*>(handle->data) = true;
104 }
105 
106 int CloseAsyncAndLoop(uv_async_t* async) {
107  bool is_closed = false;
108  async->data = &is_closed;
109  uv_close(reinterpret_cast<uv_handle_t*>(async), HandleSyncCloseCb);
110  while (!is_closed)
111  uv_run(async->loop, UV_RUN_ONCE);
112  async->data = nullptr;
113  return uv_loop_close(async->loop);
114 }
115 
116 // Delete main_thread_req_ on async handle close
117 void ReleasePairOnAsyncClose(uv_handle_t* async) {
118  AsyncAndAgent* pair = node::ContainerOf(&AsyncAndAgent::first,
119  reinterpret_cast<uv_async_t*>(async));
120  delete pair;
121 }
122 
123 } // namespace
124 
125 std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string& message) {
126  UnicodeString utf16 =
127  UnicodeString::fromUTF8(StringPiece(message.data(), message.length()));
128  StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()),
129  utf16.length());
130  return StringBuffer::create(view);
131 }
132 
133 
135  public:
136  explicit IoSessionDelegate(InspectorIo* io) : io_(io) { }
137  bool WaitForFrontendMessageWhilePaused() override;
138  void SendMessageToFrontend(const v8_inspector::StringView& message) override;
139  private:
140  InspectorIo* io_;
141 };
142 
143 // Passed to InspectorSocketServer to handle WS inspector protocol events,
144 // mostly session start, message received, and session end.
146  public:
147  InspectorIoDelegate(InspectorIo* io, const std::string& script_path,
148  const std::string& script_name, bool wait);
149  // Calls PostIncomingMessage() with appropriate InspectorAction:
150  // kStartSession
151  bool StartSession(int session_id, const std::string& target_id) override;
152  // kSendMessage
153  void MessageReceived(int session_id, const std::string& message) override;
154  // kEndSession
155  void EndSession(int session_id) override;
156 
157  std::vector<std::string> GetTargetIds() override;
158  std::string GetTargetTitle(const std::string& id) override;
159  std::string GetTargetUrl(const std::string& id) override;
160  bool IsConnected() { return connected_; }
161  void ServerDone() override {
162  io_->ServerDone();
163  }
164 
165  private:
166  InspectorIo* io_;
167  bool connected_;
168  int session_id_;
169  const std::string script_name_;
170  const std::string script_path_;
171  const std::string target_id_;
172  bool waiting_;
173 };
174 
175 void InterruptCallback(v8::Isolate*, void* agent) {
176  InspectorIo* io = static_cast<Agent*>(agent)->io();
177  if (io != nullptr)
178  io->DispatchMessages();
179 }
180 
181 class DispatchMessagesTask : public v8::Task {
182  public:
183  explicit DispatchMessagesTask(Agent* agent) : agent_(agent) {}
184 
185  void Run() override {
186  InspectorIo* io = agent_->io();
187  if (io != nullptr)
188  io->DispatchMessages();
189  }
190 
191  private:
192  Agent* agent_;
193 };
194 
195 InspectorIo::InspectorIo(Environment* env, v8::Platform* platform,
196  const std::string& path, const DebugOptions& options,
197  bool wait_for_connect)
198  : options_(options), thread_(), delegate_(nullptr),
199  state_(State::kNew), parent_env_(env),
200  thread_req_(), platform_(platform),
201  dispatching_messages_(false), session_id_(0),
202  script_name_(path),
203  wait_for_connect_(wait_for_connect), port_(-1) {
204  main_thread_req_ = new AsyncAndAgent({uv_async_t(), env->inspector_agent()});
205  CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_->first,
206  InspectorIo::MainThreadReqAsyncCb));
207  uv_unref(reinterpret_cast<uv_handle_t*>(&main_thread_req_->first));
208  CHECK_EQ(0, uv_sem_init(&thread_start_sem_, 0));
209 }
210 
212  uv_sem_destroy(&thread_start_sem_);
213  uv_close(reinterpret_cast<uv_handle_t*>(&main_thread_req_->first),
214  ReleasePairOnAsyncClose);
215 }
216 
218  CHECK_EQ(state_, State::kNew);
219  CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0);
220  uv_sem_wait(&thread_start_sem_);
221 
222  if (state_ == State::kError) {
223  return false;
224  }
225  state_ = State::kAccepting;
226  if (wait_for_connect_) {
227  DispatchMessages();
228  }
229  return true;
230 }
231 
233  CHECK(state_ == State::kAccepting || state_ == State::kConnected);
234  Write(TransportAction::kKill, 0, StringView());
235  int err = uv_thread_join(&thread_);
236  CHECK_EQ(err, 0);
237  state_ = State::kShutDown;
238  DispatchMessages();
239 }
240 
242  return delegate_ != nullptr && delegate_->IsConnected();
243 }
244 
246  return platform_ != nullptr;
247 }
248 
250  if (state_ == State::kAccepting)
251  state_ = State::kDone;
252  if (state_ == State::kConnected) {
253  state_ = State::kShutDown;
254  Write(TransportAction::kStop, 0, StringView());
255  fprintf(stderr, "Waiting for the debugger to disconnect...\n");
256  fflush(stderr);
257  parent_env_->inspector_agent()->RunMessageLoop();
258  }
259 }
260 
261 // static
262 void InspectorIo::ThreadMain(void* io) {
263  static_cast<InspectorIo*>(io)->ThreadMain<InspectorSocketServer>();
264 }
265 
266 // static
267 template <typename Transport>
268 void InspectorIo::IoThreadAsyncCb(uv_async_t* async) {
269  TransportAndIo<Transport>* transport_and_io =
270  static_cast<TransportAndIo<Transport>*>(async->data);
271  if (transport_and_io == nullptr) {
272  return;
273  }
274  Transport* transport = transport_and_io->first;
275  InspectorIo* io = transport_and_io->second;
276  MessageQueue<TransportAction> outgoing_message_queue;
277  io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_message_queue);
278  for (const auto& outgoing : outgoing_message_queue) {
279  switch (std::get<0>(outgoing)) {
281  transport->TerminateConnections();
282  // Fallthrough
284  transport->Stop(nullptr);
285  break;
287  std::string message = StringViewToUtf8(std::get<2>(outgoing)->string());
288  transport->Send(std::get<1>(outgoing), message);
289  break;
290  }
291  }
292 }
293 
294 template<typename Transport>
295 void InspectorIo::ThreadMain() {
296  uv_loop_t loop;
297  loop.data = nullptr;
298  int err = uv_loop_init(&loop);
299  CHECK_EQ(err, 0);
300  thread_req_.data = nullptr;
301  err = uv_async_init(&loop, &thread_req_, IoThreadAsyncCb<Transport>);
302  CHECK_EQ(err, 0);
303  std::string script_path = ScriptPath(&loop, script_name_);
304  InspectorIoDelegate delegate(this, script_path, script_name_,
305  wait_for_connect_);
306  delegate_ = &delegate;
307  Transport server(&delegate, &loop, options_.host_name(), options_.port());
308  TransportAndIo<Transport> queue_transport(&server, this);
309  thread_req_.data = &queue_transport;
310  if (!server.Start()) {
311  state_ = State::kError; // Safe, main thread is waiting on semaphore
312  CHECK_EQ(0, CloseAsyncAndLoop(&thread_req_));
313  uv_sem_post(&thread_start_sem_);
314  return;
315  }
316  port_ = server.Port(); // Safe, main thread is waiting on semaphore.
317  if (!wait_for_connect_) {
318  uv_sem_post(&thread_start_sem_);
319  }
320  uv_run(&loop, UV_RUN_DEFAULT);
321  thread_req_.data = nullptr;
322  CHECK_EQ(uv_loop_close(&loop), 0);
323  delegate_ = nullptr;
324 }
325 
326 template <typename ActionType>
327 bool InspectorIo::AppendMessage(MessageQueue<ActionType>* queue,
328  ActionType action, int session_id,
329  std::unique_ptr<StringBuffer> buffer) {
330  Mutex::ScopedLock scoped_lock(state_lock_);
331  bool trigger_pumping = queue->empty();
332  queue->push_back(std::make_tuple(action, session_id, std::move(buffer)));
333  return trigger_pumping;
334 }
335 
336 template <typename ActionType>
337 void InspectorIo::SwapBehindLock(MessageQueue<ActionType>* vector1,
338  MessageQueue<ActionType>* vector2) {
339  Mutex::ScopedLock scoped_lock(state_lock_);
340  vector1->swap(*vector2);
341 }
342 
344  const std::string& message) {
345  if (AppendMessage(&incoming_message_queue_, action, session_id,
346  Utf8ToStringView(message))) {
347  Agent* agent = main_thread_req_->second;
348  v8::Isolate* isolate = parent_env_->isolate();
349  platform_->CallOnForegroundThread(isolate,
350  new DispatchMessagesTask(agent));
351  isolate->RequestInterrupt(InterruptCallback, agent);
352  CHECK_EQ(0, uv_async_send(&main_thread_req_->first));
353  }
354  NotifyMessageReceived();
355 }
356 
357 std::vector<std::string> InspectorIo::GetTargetIds() const {
358  return delegate_ ? delegate_->GetTargetIds() : std::vector<std::string>();
359 }
360 
361 void InspectorIo::WaitForFrontendMessageWhilePaused() {
362  dispatching_messages_ = false;
363  Mutex::ScopedLock scoped_lock(state_lock_);
364  if (incoming_message_queue_.empty())
365  incoming_message_cond_.Wait(scoped_lock);
366 }
367 
368 void InspectorIo::NotifyMessageReceived() {
369  Mutex::ScopedLock scoped_lock(state_lock_);
370  incoming_message_cond_.Broadcast(scoped_lock);
371 }
372 
373 void InspectorIo::DispatchMessages() {
374  // This function can be reentered if there was an incoming message while
375  // V8 was processing another inspector request (e.g. if the user is
376  // evaluating a long-running JS code snippet). This can happen only at
377  // specific points (e.g. the lines that call inspector_ methods)
378  if (dispatching_messages_)
379  return;
380  dispatching_messages_ = true;
381  bool had_messages = false;
382  do {
383  if (dispatching_message_queue_.empty())
384  SwapBehindLock(&incoming_message_queue_, &dispatching_message_queue_);
385  had_messages = !dispatching_message_queue_.empty();
386  while (!dispatching_message_queue_.empty()) {
387  MessageQueue<InspectorAction>::value_type task;
388  std::swap(dispatching_message_queue_.front(), task);
389  dispatching_message_queue_.pop_front();
390  StringView message = std::get<2>(task)->string();
391  switch (std::get<0>(task)) {
393  CHECK_EQ(session_delegate_, nullptr);
394  session_id_ = std::get<1>(task);
395  state_ = State::kConnected;
396  fprintf(stderr, "Debugger attached.\n");
397  session_delegate_ = std::unique_ptr<InspectorSessionDelegate>(
398  new IoSessionDelegate(this));
399  parent_env_->inspector_agent()->Connect(session_delegate_.get());
400  break;
402  CHECK_NE(session_delegate_, nullptr);
403  if (state_ == State::kShutDown) {
404  state_ = State::kDone;
405  } else {
406  state_ = State::kAccepting;
407  }
408  parent_env_->inspector_agent()->Disconnect();
409  session_delegate_.reset();
410  break;
412  parent_env_->inspector_agent()->Dispatch(message);
413  break;
414  }
415  }
416  } while (had_messages);
417  dispatching_messages_ = false;
418 }
419 
420 // static
421 void InspectorIo::MainThreadReqAsyncCb(uv_async_t* req) {
422  AsyncAndAgent* pair = node::ContainerOf(&AsyncAndAgent::first, req);
423  // Note that this may be called after io was closed or even after a new
424  // one was created and ran.
425  InspectorIo* io = pair->second->io();
426  if (io != nullptr)
427  io->DispatchMessages();
428 }
429 
430 void InspectorIo::Write(TransportAction action, int session_id,
431  const StringView& inspector_message) {
432  AppendMessage(&outgoing_message_queue_, action, session_id,
433  StringBuffer::create(inspector_message));
434  int err = uv_async_send(&thread_req_);
435  CHECK_EQ(0, err);
436 }
437 
439  const std::string& script_path,
440  const std::string& script_name,
441  bool wait)
442  : io_(io),
443  connected_(false),
444  session_id_(0),
445  script_name_(script_name),
446  script_path_(script_path),
447  target_id_(GenerateID()),
448  waiting_(wait) { }
449 
450 
452  const std::string& target_id) {
453  if (connected_)
454  return false;
455  connected_ = true;
456  session_id_++;
458  return true;
459 }
460 
462  const std::string& message) {
463  // TODO(pfeldman): Instead of blocking execution while debugger
464  // engages, node should wait for the run callback from the remote client
465  // and initiate its startup. This is a change to node.cc that should be
466  // upstreamed separately.
467  if (waiting_) {
468  if (message.find("\"Runtime.runIfWaitingForDebugger\"") !=
469  std::string::npos) {
470  waiting_ = false;
471  io_->ResumeStartup();
472  }
473  }
475  message);
476 }
477 
478 void InspectorIoDelegate::EndSession(int session_id) {
479  connected_ = false;
481 }
482 
483 std::vector<std::string> InspectorIoDelegate::GetTargetIds() {
484  return { target_id_ };
485 }
486 
487 std::string InspectorIoDelegate::GetTargetTitle(const std::string& id) {
488  return script_name_.empty() ? GetProcessTitle() : script_name_;
489 }
490 
491 std::string InspectorIoDelegate::GetTargetUrl(const std::string& id) {
492  return "file://" + script_path_;
493 }
494 
496  io_->WaitForFrontendMessageWhilePaused();
497  return true;
498 }
499 
501  const v8_inspector::StringView& message) {
502  io_->Write(TransportAction::kSendMessage, io_->session_id_, message);
503 }
504 
505 } // namespace inspector
506 } // namespace node
std::unique_ptr< StringBuffer > Utf8ToStringView(const std::string &message)
InspectorIo(node::Environment *env, v8::Platform *platform, const std::string &path, const DebugOptions &options, bool wait_for_connect)
void InterruptCallback(v8::Isolate *, void *agent)
std::vector< std::string > GetTargetIds() override
std::string source
Definition: module_wrap.cc:306
void Broadcast(const ScopedLock &)
Definition: node_mutex.h:125
InspectorIoDelegate(InspectorIo *io, const std::string &script_path, const std::string &script_name, bool wait)
std::string GetTargetUrl(const std::string &id) override
void SendMessageToFrontend(const v8_inspector::StringView &message) override
void PostIncomingMessage(InspectorAction action, int session_id, const std::string &message)
this done
Definition: v8ustack.d:366
void EndSession(int session_id) override
friend void InterruptCallback(v8::Isolate *, void *agent)
bool WaitForFrontendMessageWhilePaused() override
void MessageReceived(int session_id, const std::string &message) override
std::vector< std::string > GetTargetIds() const
void Wait(const ScopedLock &scoped_lock)
Definition: node_mutex.h:135
uv_fs_t req
Definition: node_file.cc:374
std::string GetTargetTitle(const std::string &id) override
bool StartSession(int session_id, const std::string &target_id) override
bool EntropySource(unsigned char *buffer, size_t length)
Definition: node_crypto.cc:302
std::string host_name() const