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