Coverage Report

Created: 2025-12-10 07:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/node/src/inspector_io.cc
Line
Count
Source
1
#include "inspector_io.h"
2
3
#include "base_object-inl.h"
4
#include "crypto/crypto_util.h"
5
#include "debug_utils-inl.h"
6
#include "inspector/main_thread_interface.h"
7
#include "inspector/node_json.h"
8
#include "inspector/node_string.h"
9
#include "inspector/target_agent.h"
10
#include "inspector_socket_server.h"
11
#include "ncrypto.h"
12
#include "node.h"
13
#include "node_internals.h"
14
#include "node_mutex.h"
15
#include "util-inl.h"
16
#include "v8-inspector.h"
17
#include "zlib.h"
18
19
#include <deque>
20
#include <cstring>
21
#include <vector>
22
23
namespace node {
24
namespace inspector {
25
namespace {
26
using v8_inspector::StringBuffer;
27
using v8_inspector::StringView;
28
29
// kKill closes connections and stops the server, kStop only stops the server
30
enum class TransportAction { kKill, kSendMessage, kStop };
31
32
0
std::string ScriptPath(uv_loop_t* loop, const std::string& script_name) {
33
0
  std::string script_path;
34
35
0
  if (!script_name.empty()) {
36
0
    uv_fs_t req;
37
0
    req.ptr = nullptr;
38
0
    if (0 == uv_fs_realpath(loop, &req, script_name.c_str(), nullptr)) {
39
0
      CHECK_NOT_NULL(req.ptr);
40
0
      script_path = std::string(static_cast<char*>(req.ptr));
41
0
    }
42
0
    uv_fs_req_cleanup(&req);
43
0
  }
44
45
0
  return script_path;
46
0
}
47
48
// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
49
// Used ver 4 - with numbers
50
0
std::string GenerateID() {
51
0
  uint16_t buffer[8];
52
0
  CHECK(ncrypto::CSPRNG(buffer, sizeof(buffer)));
53
54
0
  char uuid[256];
55
0
  snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
56
0
           buffer[0],  // time_low
57
0
           buffer[1],  // time_mid
58
0
           buffer[2],  // time_low
59
0
           (buffer[3] & 0x0fff) | 0x4000,  // time_hi_and_version
60
0
           (buffer[4] & 0x3fff) | 0x8000,  // clk_seq_hi clk_seq_low
61
0
           buffer[5],  // node
62
0
           buffer[6],
63
0
           buffer[7]);
64
0
  return uuid;
65
0
}
66
67
class RequestToServer {
68
 public:
69
  RequestToServer(TransportAction action,
70
                  int session_id,
71
                  std::unique_ptr<v8_inspector::StringBuffer> message)
72
0
                  : action_(action),
73
0
                    session_id_(session_id),
74
0
                    message_(std::move(message)) {}
75
76
0
  void Dispatch(InspectorSocketServer* server) const {
77
0
    switch (action_) {
78
0
      case TransportAction::kKill:
79
0
        server->TerminateConnections();
80
0
        [[fallthrough]];
81
0
      case TransportAction::kStop:
82
0
        server->Stop();
83
0
        break;
84
0
      case TransportAction::kSendMessage:
85
0
        server->Send(
86
0
            session_id_,
87
0
            protocol::StringUtil::StringViewToUtf8(message_->string()));
88
0
        break;
89
0
    }
90
0
  }
91
92
 private:
93
  TransportAction action_;
94
  int session_id_;
95
  std::unique_ptr<v8_inspector::StringBuffer> message_;
96
};
97
98
class RequestQueueData {
99
 public:
100
  using MessageQueue = std::deque<RequestToServer>;
101
102
  explicit RequestQueueData(uv_loop_t* loop)
103
0
                            : handle_(std::make_shared<RequestQueue>(this)) {
104
0
    int err = uv_async_init(loop, &async_, [](uv_async_t* async) {
105
0
      RequestQueueData* wrapper =
106
0
          node::ContainerOf(&RequestQueueData::async_, async);
107
0
      wrapper->DoDispatch();
108
0
    });
109
0
    CHECK_EQ(0, err);
110
0
  }
111
112
  static void CloseAndFree(RequestQueueData* queue);
113
114
  void Post(int session_id,
115
            TransportAction action,
116
0
            std::unique_ptr<StringBuffer> message) {
117
0
    Mutex::ScopedLock scoped_lock(state_lock_);
118
0
    bool notify = messages_.empty();
119
0
    messages_.emplace_back(action, session_id, std::move(message));
120
0
    if (notify) {
121
0
      CHECK_EQ(0, uv_async_send(&async_));
122
0
      incoming_message_cond_.Broadcast(scoped_lock);
123
0
    }
124
0
  }
125
126
0
  void Wait() {
127
0
    Mutex::ScopedLock scoped_lock(state_lock_);
128
0
    if (messages_.empty()) {
129
0
      incoming_message_cond_.Wait(scoped_lock);
130
0
    }
131
0
  }
132
133
0
  void SetServer(InspectorSocketServer* server) {
134
0
    server_ = server;
135
0
  }
136
137
0
  std::shared_ptr<RequestQueue> handle() {
138
0
    return handle_;
139
0
  }
140
141
 private:
142
0
  ~RequestQueueData() = default;
143
144
0
  MessageQueue GetMessages() {
145
0
    Mutex::ScopedLock scoped_lock(state_lock_);
146
0
    MessageQueue messages;
147
0
    messages_.swap(messages);
148
0
    return messages;
149
0
  }
150
151
0
  void DoDispatch() {
152
0
    if (server_ == nullptr)
153
0
      return;
154
0
    for (const auto& request : GetMessages()) {
155
0
      request.Dispatch(server_);
156
0
    }
157
0
  }
158
159
  std::shared_ptr<RequestQueue> handle_;
160
  uv_async_t async_;
161
  InspectorSocketServer* server_ = nullptr;
162
  MessageQueue messages_;
163
  Mutex state_lock_;  // Locked before mutating the queue.
164
  ConditionVariable incoming_message_cond_;
165
};
166
}  // namespace
167
168
class RequestQueue {
169
 public:
170
0
  explicit RequestQueue(RequestQueueData* data) : data_(data) {}
171
172
0
  void Reset() {
173
0
    Mutex::ScopedLock scoped_lock(lock_);
174
0
    data_ = nullptr;
175
0
  }
176
177
  void Post(int session_id,
178
            TransportAction action,
179
0
            std::unique_ptr<StringBuffer> message) {
180
0
    Mutex::ScopedLock scoped_lock(lock_);
181
0
    if (data_ != nullptr)
182
0
      data_->Post(session_id, action, std::move(message));
183
0
  }
184
185
0
  bool Expired() {
186
0
    Mutex::ScopedLock scoped_lock(lock_);
187
0
    return data_ == nullptr;
188
0
  }
189
190
 private:
191
  RequestQueueData* data_;
192
  Mutex lock_;
193
};
194
195
class IoSessionDelegate : public InspectorSessionDelegate {
196
 public:
197
  explicit IoSessionDelegate(std::shared_ptr<RequestQueue> queue, int id)
198
0
                             : request_queue_(queue), id_(id) { }
199
0
  void SendMessageToFrontend(const v8_inspector::StringView& message) override {
200
0
    request_queue_->Post(id_, TransportAction::kSendMessage,
201
0
                         StringBuffer::create(message));
202
0
  }
203
204
 private:
205
  std::shared_ptr<RequestQueue> request_queue_;
206
  int id_;
207
};
208
209
// Passed to InspectorSocketServer to handle WS inspector protocol events,
210
// mostly session start, message received, and session end.
211
class InspectorIoDelegate: public node::inspector::SocketServerDelegate {
212
 public:
213
  InspectorIoDelegate(std::shared_ptr<RequestQueueData> queue,
214
                      std::shared_ptr<MainThreadHandle> main_thread,
215
                      const std::string& target_id,
216
                      const std::string& script_path,
217
                      const std::string& script_name);
218
0
  ~InspectorIoDelegate() override = default;
219
220
  void StartSession(int session_id, const std::string& target_id) override;
221
  void MessageReceived(int session_id, const std::string& message) override;
222
  void EndSession(int session_id) override;
223
  std::optional<std::string> GetTargetSessionId(const std::string& message);
224
225
  std::vector<std::string> GetTargetIds() override;
226
  std::string GetTargetTitle(const std::string& id) override;
227
  std::string GetTargetUrl(const std::string& id) override;
228
0
  void AssignServer(InspectorSocketServer* server) override {
229
0
    request_queue_->SetServer(server);
230
0
  }
231
232
 private:
233
  std::shared_ptr<RequestQueueData> request_queue_;
234
  std::shared_ptr<MainThreadHandle> main_thread_;
235
  std::unordered_map<int, std::unique_ptr<InspectorSession>> sessions_;
236
  const std::string script_name_;
237
  const std::string script_path_;
238
  const std::string target_id_;
239
};
240
241
// static
242
std::unique_ptr<InspectorIo> InspectorIo::Start(
243
    std::shared_ptr<MainThreadHandle> main_thread,
244
    const std::string& path,
245
    std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
246
0
    const InspectPublishUid& inspect_publish_uid) {
247
0
  auto io = std::unique_ptr<InspectorIo>(
248
0
      new InspectorIo(main_thread,
249
0
                      path,
250
0
                      host_port,
251
0
                      inspect_publish_uid));
252
0
  if (io->request_queue_->Expired()) {  // Thread is not running
253
0
    return nullptr;
254
0
  }
255
0
  return io;
256
0
}
257
258
InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> main_thread,
259
                         const std::string& path,
260
                         std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
261
                         const InspectPublishUid& inspect_publish_uid)
262
0
    : main_thread_(main_thread),
263
0
      host_port_(host_port),
264
0
      inspect_publish_uid_(inspect_publish_uid),
265
      thread_(),
266
0
      script_name_(path),
267
0
      id_(GenerateID()) {
268
0
  Mutex::ScopedLock scoped_lock(thread_start_lock_);
269
0
  CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0);
270
0
  thread_start_condition_.Wait(scoped_lock);
271
0
}
272
273
0
InspectorIo::~InspectorIo() {
274
0
  request_queue_->Post(0, TransportAction::kKill, nullptr);
275
0
  int err = uv_thread_join(&thread_);
276
0
  CHECK_EQ(err, 0);
277
0
}
278
279
0
void InspectorIo::StopAcceptingNewConnections() {
280
0
  request_queue_->Post(0, TransportAction::kStop, nullptr);
281
0
}
282
283
// static
284
0
void InspectorIo::ThreadMain(void* io) {
285
0
  static_cast<InspectorIo*>(io)->ThreadMain();
286
0
}
287
288
0
void InspectorIo::ThreadMain() {
289
0
  int thread_name_error = uv_thread_setname("InspectorIo");
290
0
  if (!thread_name_error) [[unlikely]] {
291
0
    per_process::Debug(node::DebugCategory::INSPECTOR_SERVER,
292
0
                       "Failed to set thread name for Inspector\n");
293
0
  }
294
0
  uv_loop_t loop;
295
0
  loop.data = nullptr;
296
0
  int err = uv_loop_init(&loop);
297
0
  CHECK_EQ(err, 0);
298
0
  std::shared_ptr<RequestQueueData> queue(new RequestQueueData(&loop),
299
0
                                          RequestQueueData::CloseAndFree);
300
0
  std::string script_path = ScriptPath(&loop, script_name_);
301
0
  std::unique_ptr<InspectorIoDelegate> delegate(
302
0
      new InspectorIoDelegate(queue, main_thread_, id_,
303
0
                              script_path, script_name_));
304
0
  std::string host;
305
0
  int port;
306
0
  {
307
0
    ExclusiveAccess<HostPort>::Scoped host_port(host_port_);
308
0
    host = host_port->host();
309
0
    port = host_port->port();
310
0
  }
311
0
  InspectorSocketServer server(std::move(delegate),
312
0
                               &loop,
313
0
                               std::move(host),
314
0
                               port,
315
0
                               inspect_publish_uid_);
316
0
  request_queue_ = queue->handle();
317
  // Its lifetime is now that of the server delegate
318
0
  queue.reset();
319
0
  {
320
0
    Mutex::ScopedLock scoped_lock(thread_start_lock_);
321
0
    if (server.Start()) {
322
0
      ExclusiveAccess<HostPort>::Scoped host_port(host_port_);
323
0
      host_port->set_port(server.Port());
324
0
    }
325
0
    thread_start_condition_.Broadcast(scoped_lock);
326
0
  }
327
0
  uv_run(&loop, UV_RUN_DEFAULT);
328
0
  CheckedUvLoopClose(&loop);
329
0
}
330
331
0
std::string InspectorIo::GetWsUrl() const {
332
0
  ExclusiveAccess<HostPort>::Scoped host_port(host_port_);
333
0
  return FormatWsAddress(host_port->host(), host_port->port(), id_, true);
334
0
}
335
336
InspectorIoDelegate::InspectorIoDelegate(
337
    std::shared_ptr<RequestQueueData> queue,
338
    std::shared_ptr<MainThreadHandle> main_thread,
339
    const std::string& target_id,
340
    const std::string& script_path,
341
    const std::string& script_name)
342
0
    : request_queue_(queue), main_thread_(main_thread),
343
0
      script_name_(script_name), script_path_(script_path),
344
0
      target_id_(target_id) {}
345
346
void InspectorIoDelegate::StartSession(int session_id,
347
0
                                       const std::string& target_id) {
348
0
  fprintf(stderr, "Debugger attached.\n");
349
0
}
350
351
std::optional<std::string> InspectorIoDelegate::GetTargetSessionId(
352
0
    const std::string& message) {
353
0
  std::string_view view(message.data(), message.size());
354
0
  std::unique_ptr<protocol::DictionaryValue> value =
355
0
      protocol::DictionaryValue::cast(JsonUtil::parseJSON(view));
356
0
  if (!value) {
357
0
    return std::nullopt;
358
0
  }
359
0
  protocol::String target_session_id;
360
0
  protocol::Value* target_session_id_value = value->get("sessionId");
361
0
  if (target_session_id_value) {
362
0
    target_session_id_value->asString(&target_session_id);
363
0
  }
364
365
0
  if (!target_session_id.empty()) {
366
0
    return target_session_id;
367
0
  }
368
0
  return std::nullopt;
369
0
}
370
371
void InspectorIoDelegate::MessageReceived(int session_id,
372
0
                                          const std::string& message) {
373
0
  std::optional<std::string> target_session_id_str =
374
0
      GetTargetSessionId(message);
375
0
  std::shared_ptr<MainThreadHandle> worker = nullptr;
376
0
  int merged_session_id = session_id;
377
0
  if (target_session_id_str) {
378
0
    bool is_number = std::all_of(target_session_id_str->begin(),
379
0
                                 target_session_id_str->end(),
380
0
                                 ::isdigit);
381
0
    if (is_number) {
382
0
      int target_session_id = std::stoi(*target_session_id_str);
383
0
      worker = protocol::TargetAgent::target_session_id_worker_map_
384
0
          [target_session_id];
385
0
      if (worker) {
386
0
        merged_session_id += target_session_id << 16;
387
0
      }
388
0
    }
389
0
  }
390
391
0
  auto session = sessions_.find(merged_session_id);
392
393
0
  if (session == sessions_.end()) {
394
0
    std::unique_ptr<InspectorSession> session;
395
0
    if (worker) {
396
0
      session = worker->Connect(
397
0
          std::unique_ptr<InspectorSessionDelegate>(
398
0
              new IoSessionDelegate(request_queue_->handle(), session_id)),
399
0
          true);
400
0
    } else {
401
0
      session = main_thread_->Connect(
402
0
          std::unique_ptr<InspectorSessionDelegate>(
403
0
              new IoSessionDelegate(request_queue_->handle(), session_id)),
404
0
          true);
405
0
    }
406
407
0
    if (session) {
408
0
      sessions_[merged_session_id] = std::move(session);
409
0
      sessions_[merged_session_id]->Dispatch(
410
0
          Utf8ToStringView(message)->string());
411
0
    } else {
412
0
      fprintf(stderr, "Failed to connect to inspector session.\n");
413
0
    }
414
0
  } else {
415
0
    session->second->Dispatch(Utf8ToStringView(message)->string());
416
0
  }
417
0
}
418
419
0
void InspectorIoDelegate::EndSession(int session_id) {
420
0
  sessions_.erase(session_id);
421
0
}
422
423
0
std::vector<std::string> InspectorIoDelegate::GetTargetIds() {
424
0
  return { target_id_ };
425
0
}
426
427
0
std::string InspectorIoDelegate::GetTargetTitle(const std::string& id) {
428
0
  return script_name_.empty() ? GetHumanReadableProcessName() : script_name_;
429
0
}
430
431
0
std::string InspectorIoDelegate::GetTargetUrl(const std::string& id) {
432
0
  return "file://" + script_path_;
433
0
}
434
435
// static
436
0
void RequestQueueData::CloseAndFree(RequestQueueData* queue) {
437
0
  queue->handle_->Reset();
438
0
  queue->handle_.reset();
439
0
  uv_close(reinterpret_cast<uv_handle_t*>(&queue->async_),
440
0
           [](uv_handle_t* handle) {
441
0
    uv_async_t* async = reinterpret_cast<uv_async_t*>(handle);
442
0
    RequestQueueData* wrapper =
443
0
        node::ContainerOf(&RequestQueueData::async_, async);
444
0
    delete wrapper;
445
0
  });
446
0
}
447
}  // namespace inspector
448
}  // namespace node