1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/inspector/v8-debugger.h"
6
7 #include "include/v8-container.h"
8 #include "include/v8-context.h"
9 #include "include/v8-function.h"
10 #include "include/v8-microtask-queue.h"
11 #include "include/v8-util.h"
12 #include "src/inspector/inspected-context.h"
13 #include "src/inspector/protocol/Protocol.h"
14 #include "src/inspector/string-util.h"
15 #include "src/inspector/v8-debugger-agent-impl.h"
16 #include "src/inspector/v8-inspector-impl.h"
17 #include "src/inspector/v8-inspector-session-impl.h"
18 #include "src/inspector/v8-runtime-agent-impl.h"
19 #include "src/inspector/v8-stack-trace-impl.h"
20 #include "src/inspector/v8-value-utils.h"
21
22 namespace v8_inspector {
23
24 namespace {
25
26 static const size_t kMaxAsyncTaskStacks = 8 * 1024;
27 static const int kNoBreakpointId = 0;
28
29 template <typename Map>
cleanupExpiredWeakPointers(Map & map)30 void cleanupExpiredWeakPointers(Map& map) {
31 for (auto it = map.begin(); it != map.end();) {
32 if (it->second.expired()) {
33 it = map.erase(it);
34 } else {
35 ++it;
36 }
37 }
38 }
39
40 class MatchPrototypePredicate : public v8::debug::QueryObjectPredicate {
41 public:
MatchPrototypePredicate(V8InspectorImpl * inspector,v8::Local<v8::Context> context,v8::Local<v8::Object> prototype)42 MatchPrototypePredicate(V8InspectorImpl* inspector,
43 v8::Local<v8::Context> context,
44 v8::Local<v8::Object> prototype)
45 : m_inspector(inspector), m_context(context), m_prototype(prototype) {}
46
Filter(v8::Local<v8::Object> object)47 bool Filter(v8::Local<v8::Object> object) override {
48 if (object->IsModuleNamespaceObject()) return false;
49 v8::Local<v8::Context> objectContext;
50 if (!v8::debug::GetCreationContext(object).ToLocal(&objectContext)) {
51 return false;
52 }
53 if (objectContext != m_context) return false;
54 if (!m_inspector->client()->isInspectableHeapObject(object)) return false;
55 // Get prototype chain for current object until first visited prototype.
56 for (v8::Local<v8::Value> prototype = object->GetPrototype();
57 prototype->IsObject();
58 prototype = prototype.As<v8::Object>()->GetPrototype()) {
59 if (m_prototype == prototype) return true;
60 }
61 return false;
62 }
63
64 private:
65 V8InspectorImpl* m_inspector;
66 v8::Local<v8::Context> m_context;
67 v8::Local<v8::Value> m_prototype;
68 };
69
70 } // namespace
71
V8Debugger(v8::Isolate * isolate,V8InspectorImpl * inspector)72 V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
73 : m_isolate(isolate),
74 m_inspector(inspector),
75 m_enableCount(0),
76 m_ignoreScriptParsedEventsCounter(0),
77 m_continueToLocationBreakpointId(kNoBreakpointId),
78 m_maxAsyncCallStacks(kMaxAsyncTaskStacks),
79 m_maxAsyncCallStackDepth(0),
80 m_maxCallStackSizeToCapture(
81 V8StackTraceImpl::kDefaultMaxCallStackSizeToCapture),
82 m_pauseOnExceptionsState(v8::debug::NoBreakOnException) {}
83
~V8Debugger()84 V8Debugger::~V8Debugger() {
85 m_isolate->RemoveCallCompletedCallback(
86 &V8Debugger::terminateExecutionCompletedCallback);
87 m_isolate->RemoveMicrotasksCompletedCallback(
88 &V8Debugger::terminateExecutionCompletedCallbackIgnoringData);
89 }
90
enable()91 void V8Debugger::enable() {
92 if (m_enableCount++) return;
93 v8::HandleScope scope(m_isolate);
94 v8::debug::SetDebugDelegate(m_isolate, this);
95 m_isolate->AddNearHeapLimitCallback(&V8Debugger::nearHeapLimitCallback, this);
96 v8::debug::ChangeBreakOnException(m_isolate, v8::debug::NoBreakOnException);
97 m_pauseOnExceptionsState = v8::debug::NoBreakOnException;
98 #if V8_ENABLE_WEBASSEMBLY
99 v8::debug::TierDownAllModulesPerIsolate(m_isolate);
100 #endif // V8_ENABLE_WEBASSEMBLY
101 }
102
disable()103 void V8Debugger::disable() {
104 if (isPaused()) {
105 bool scheduledOOMBreak = m_scheduledOOMBreak;
106 bool hasAgentAcceptsPause = false;
107 m_inspector->forEachSession(
108 m_pausedContextGroupId, [&scheduledOOMBreak, &hasAgentAcceptsPause](
109 V8InspectorSessionImpl* session) {
110 if (session->debuggerAgent()->acceptsPause(scheduledOOMBreak)) {
111 hasAgentAcceptsPause = true;
112 }
113 });
114 if (!hasAgentAcceptsPause) m_inspector->client()->quitMessageLoopOnPause();
115 }
116 if (--m_enableCount) return;
117 clearContinueToLocation();
118 m_taskWithScheduledBreak = nullptr;
119 m_externalAsyncTaskPauseRequested = false;
120 m_taskWithScheduledBreakPauseRequested = false;
121 m_pauseOnNextCallRequested = false;
122 m_pauseOnAsyncCall = false;
123 #if V8_ENABLE_WEBASSEMBLY
124 v8::debug::TierUpAllModulesPerIsolate(m_isolate);
125 #endif // V8_ENABLE_WEBASSEMBLY
126 v8::debug::SetDebugDelegate(m_isolate, nullptr);
127 m_isolate->RemoveNearHeapLimitCallback(&V8Debugger::nearHeapLimitCallback,
128 m_originalHeapLimit);
129 m_originalHeapLimit = 0;
130 }
131
isPausedInContextGroup(int contextGroupId) const132 bool V8Debugger::isPausedInContextGroup(int contextGroupId) const {
133 return isPaused() && m_pausedContextGroupId == contextGroupId;
134 }
135
enabled() const136 bool V8Debugger::enabled() const { return m_enableCount > 0; }
137
getCompiledScripts(int contextGroupId,V8DebuggerAgentImpl * agent)138 std::vector<std::unique_ptr<V8DebuggerScript>> V8Debugger::getCompiledScripts(
139 int contextGroupId, V8DebuggerAgentImpl* agent) {
140 std::vector<std::unique_ptr<V8DebuggerScript>> result;
141 v8::HandleScope scope(m_isolate);
142 v8::PersistentValueVector<v8::debug::Script> scripts(m_isolate);
143 v8::debug::GetLoadedScripts(m_isolate, scripts);
144 for (size_t i = 0; i < scripts.Size(); ++i) {
145 v8::Local<v8::debug::Script> script = scripts.Get(i);
146 if (!script->WasCompiled()) continue;
147 if (!script->IsEmbedded()) {
148 int contextId;
149 if (!script->ContextId().To(&contextId)) continue;
150 if (m_inspector->contextGroupId(contextId) != contextGroupId) continue;
151 }
152 result.push_back(V8DebuggerScript::Create(m_isolate, script, false, agent,
153 m_inspector->client()));
154 }
155 return result;
156 }
157
setBreakpointsActive(bool active)158 void V8Debugger::setBreakpointsActive(bool active) {
159 if (!enabled()) {
160 UNREACHABLE();
161 }
162 m_breakpointsActiveCount += active ? 1 : -1;
163 v8::debug::SetBreakPointsActive(m_isolate, m_breakpointsActiveCount);
164 }
165
getPauseOnExceptionsState()166 v8::debug::ExceptionBreakState V8Debugger::getPauseOnExceptionsState() {
167 DCHECK(enabled());
168 return m_pauseOnExceptionsState;
169 }
170
setPauseOnExceptionsState(v8::debug::ExceptionBreakState pauseOnExceptionsState)171 void V8Debugger::setPauseOnExceptionsState(
172 v8::debug::ExceptionBreakState pauseOnExceptionsState) {
173 DCHECK(enabled());
174 if (m_pauseOnExceptionsState == pauseOnExceptionsState) return;
175 v8::debug::ChangeBreakOnException(m_isolate, pauseOnExceptionsState);
176 m_pauseOnExceptionsState = pauseOnExceptionsState;
177 }
178
setPauseOnNextCall(bool pause,int targetContextGroupId)179 void V8Debugger::setPauseOnNextCall(bool pause, int targetContextGroupId) {
180 if (isPaused()) return;
181 DCHECK(targetContextGroupId);
182 if (!pause && m_targetContextGroupId &&
183 m_targetContextGroupId != targetContextGroupId) {
184 return;
185 }
186 if (pause) {
187 bool didHaveBreak = hasScheduledBreakOnNextFunctionCall();
188 m_pauseOnNextCallRequested = true;
189 if (!didHaveBreak) {
190 m_targetContextGroupId = targetContextGroupId;
191 v8::debug::SetBreakOnNextFunctionCall(m_isolate);
192 }
193 } else {
194 m_pauseOnNextCallRequested = false;
195 if (!hasScheduledBreakOnNextFunctionCall()) {
196 v8::debug::ClearBreakOnNextFunctionCall(m_isolate);
197 }
198 }
199 }
200
canBreakProgram()201 bool V8Debugger::canBreakProgram() {
202 return v8::debug::CanBreakProgram(m_isolate);
203 }
204
breakProgram(int targetContextGroupId)205 void V8Debugger::breakProgram(int targetContextGroupId) {
206 DCHECK(canBreakProgram());
207 // Don't allow nested breaks.
208 if (isPaused()) return;
209 DCHECK(targetContextGroupId);
210 m_targetContextGroupId = targetContextGroupId;
211 v8::debug::BreakRightNow(m_isolate);
212 }
213
interruptAndBreak(int targetContextGroupId)214 void V8Debugger::interruptAndBreak(int targetContextGroupId) {
215 // Don't allow nested breaks.
216 if (isPaused()) return;
217 DCHECK(targetContextGroupId);
218 m_targetContextGroupId = targetContextGroupId;
219 m_isolate->RequestInterrupt(
220 [](v8::Isolate* isolate, void*) {
221 v8::debug::BreakRightNow(
222 isolate,
223 v8::debug::BreakReasons({v8::debug::BreakReason::kScheduled}));
224 },
225 nullptr);
226 }
227
continueProgram(int targetContextGroupId,bool terminateOnResume)228 void V8Debugger::continueProgram(int targetContextGroupId,
229 bool terminateOnResume) {
230 if (m_pausedContextGroupId != targetContextGroupId) return;
231 if (isPaused()) {
232 if (terminateOnResume) {
233 v8::debug::SetTerminateOnResume(m_isolate);
234 }
235 m_inspector->client()->quitMessageLoopOnPause();
236 }
237 }
238
breakProgramOnAssert(int targetContextGroupId)239 void V8Debugger::breakProgramOnAssert(int targetContextGroupId) {
240 if (!enabled()) return;
241 if (m_pauseOnExceptionsState == v8::debug::NoBreakOnException) return;
242 // Don't allow nested breaks.
243 if (isPaused()) return;
244 if (!canBreakProgram()) return;
245 DCHECK(targetContextGroupId);
246 m_targetContextGroupId = targetContextGroupId;
247 v8::debug::BreakRightNow(
248 m_isolate, v8::debug::BreakReasons({v8::debug::BreakReason::kAssert}));
249 }
250
stepIntoStatement(int targetContextGroupId,bool breakOnAsyncCall)251 void V8Debugger::stepIntoStatement(int targetContextGroupId,
252 bool breakOnAsyncCall) {
253 DCHECK(isPaused());
254 DCHECK(targetContextGroupId);
255 m_targetContextGroupId = targetContextGroupId;
256 m_pauseOnAsyncCall = breakOnAsyncCall;
257 v8::debug::PrepareStep(m_isolate, v8::debug::StepInto);
258 continueProgram(targetContextGroupId);
259 }
260
stepOverStatement(int targetContextGroupId)261 void V8Debugger::stepOverStatement(int targetContextGroupId) {
262 DCHECK(isPaused());
263 DCHECK(targetContextGroupId);
264 m_targetContextGroupId = targetContextGroupId;
265 v8::debug::PrepareStep(m_isolate, v8::debug::StepOver);
266 continueProgram(targetContextGroupId);
267 }
268
stepOutOfFunction(int targetContextGroupId)269 void V8Debugger::stepOutOfFunction(int targetContextGroupId) {
270 DCHECK(isPaused());
271 DCHECK(targetContextGroupId);
272 m_targetContextGroupId = targetContextGroupId;
273 v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
274 continueProgram(targetContextGroupId);
275 }
276
terminateExecution(std::unique_ptr<TerminateExecutionCallback> callback)277 void V8Debugger::terminateExecution(
278 std::unique_ptr<TerminateExecutionCallback> callback) {
279 if (m_terminateExecutionCallback) {
280 if (callback) {
281 callback->sendFailure(Response::ServerError(
282 "There is current termination request in progress"));
283 }
284 return;
285 }
286 m_terminateExecutionCallback = std::move(callback);
287 m_isolate->AddCallCompletedCallback(
288 &V8Debugger::terminateExecutionCompletedCallback);
289 m_isolate->AddMicrotasksCompletedCallback(
290 &V8Debugger::terminateExecutionCompletedCallbackIgnoringData);
291 m_isolate->TerminateExecution();
292 }
293
reportTermination()294 void V8Debugger::reportTermination() {
295 if (!m_terminateExecutionCallback) return;
296 m_isolate->RemoveCallCompletedCallback(
297 &V8Debugger::terminateExecutionCompletedCallback);
298 m_isolate->RemoveMicrotasksCompletedCallback(
299 &V8Debugger::terminateExecutionCompletedCallbackIgnoringData);
300 m_isolate->CancelTerminateExecution();
301 m_terminateExecutionCallback->sendSuccess();
302 m_terminateExecutionCallback.reset();
303 }
304
terminateExecutionCompletedCallback(v8::Isolate * isolate)305 void V8Debugger::terminateExecutionCompletedCallback(v8::Isolate* isolate) {
306 V8InspectorImpl* inspector =
307 static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
308 V8Debugger* debugger = inspector->debugger();
309 debugger->reportTermination();
310 }
311
terminateExecutionCompletedCallbackIgnoringData(v8::Isolate * isolate,void *)312 void V8Debugger::terminateExecutionCompletedCallbackIgnoringData(
313 v8::Isolate* isolate, void*) {
314 terminateExecutionCompletedCallback(isolate);
315 }
316
continueToLocation(int targetContextGroupId,V8DebuggerScript * script,std::unique_ptr<protocol::Debugger::Location> location,const String16 & targetCallFrames)317 Response V8Debugger::continueToLocation(
318 int targetContextGroupId, V8DebuggerScript* script,
319 std::unique_ptr<protocol::Debugger::Location> location,
320 const String16& targetCallFrames) {
321 DCHECK(isPaused());
322 DCHECK(targetContextGroupId);
323 m_targetContextGroupId = targetContextGroupId;
324 v8::debug::Location v8Location(location->getLineNumber(),
325 location->getColumnNumber(0));
326 if (script->setBreakpoint(String16(), &v8Location,
327 &m_continueToLocationBreakpointId)) {
328 m_continueToLocationTargetCallFrames = targetCallFrames;
329 if (m_continueToLocationTargetCallFrames !=
330 protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any) {
331 m_continueToLocationStack = V8StackTraceImpl::capture(
332 this, V8StackTraceImpl::kDefaultMaxCallStackSizeToCapture);
333 DCHECK(m_continueToLocationStack);
334 }
335 continueProgram(targetContextGroupId);
336 // TODO(kozyatinskiy): Return actual line and column number.
337 return Response::Success();
338 } else {
339 return Response::ServerError("Cannot continue to specified location");
340 }
341 }
342
shouldContinueToCurrentLocation()343 bool V8Debugger::shouldContinueToCurrentLocation() {
344 if (m_continueToLocationTargetCallFrames ==
345 protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any) {
346 return true;
347 }
348 std::unique_ptr<V8StackTraceImpl> currentStack = V8StackTraceImpl::capture(
349 this, V8StackTraceImpl::kDefaultMaxCallStackSizeToCapture);
350 if (m_continueToLocationTargetCallFrames ==
351 protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Current) {
352 return m_continueToLocationStack->isEqualIgnoringTopFrame(
353 currentStack.get());
354 }
355 return true;
356 }
357
clearContinueToLocation()358 void V8Debugger::clearContinueToLocation() {
359 if (m_continueToLocationBreakpointId == kNoBreakpointId) return;
360 v8::debug::RemoveBreakpoint(m_isolate, m_continueToLocationBreakpointId);
361 m_continueToLocationBreakpointId = kNoBreakpointId;
362 m_continueToLocationTargetCallFrames = String16();
363 m_continueToLocationStack.reset();
364 }
365
handleProgramBreak(v8::Local<v8::Context> pausedContext,v8::Local<v8::Value> exception,const std::vector<v8::debug::BreakpointId> & breakpointIds,v8::debug::BreakReasons breakReasons,v8::debug::ExceptionType exceptionType,bool isUncaught)366 void V8Debugger::handleProgramBreak(
367 v8::Local<v8::Context> pausedContext, v8::Local<v8::Value> exception,
368 const std::vector<v8::debug::BreakpointId>& breakpointIds,
369 v8::debug::BreakReasons breakReasons,
370 v8::debug::ExceptionType exceptionType, bool isUncaught) {
371 // Don't allow nested breaks.
372 if (isPaused()) return;
373
374 int contextGroupId = m_inspector->contextGroupId(pausedContext);
375 if (m_targetContextGroupId && contextGroupId != m_targetContextGroupId) {
376 v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
377 return;
378 }
379
380 DCHECK(hasScheduledBreakOnNextFunctionCall() ==
381 (m_taskWithScheduledBreakPauseRequested ||
382 m_externalAsyncTaskPauseRequested || m_pauseOnNextCallRequested));
383 if (m_taskWithScheduledBreakPauseRequested ||
384 m_externalAsyncTaskPauseRequested)
385 breakReasons.Add(v8::debug::BreakReason::kAsyncStep);
386 if (m_pauseOnNextCallRequested)
387 breakReasons.Add(v8::debug::BreakReason::kAgent);
388
389 m_targetContextGroupId = 0;
390 m_pauseOnNextCallRequested = false;
391 m_pauseOnAsyncCall = false;
392 m_taskWithScheduledBreak = nullptr;
393 m_externalAsyncTaskPauseRequested = false;
394 m_taskWithScheduledBreakPauseRequested = false;
395
396 bool scheduledOOMBreak = m_scheduledOOMBreak;
397 DCHECK(scheduledOOMBreak ==
398 breakReasons.contains(v8::debug::BreakReason::kOOM));
399 bool hasAgents = false;
400
401 m_inspector->forEachSession(
402 contextGroupId,
403 [&scheduledOOMBreak, &hasAgents](V8InspectorSessionImpl* session) {
404 if (session->debuggerAgent()->acceptsPause(scheduledOOMBreak))
405 hasAgents = true;
406 });
407 if (!hasAgents) return;
408
409 if (breakpointIds.size() == 1 &&
410 breakpointIds[0] == m_continueToLocationBreakpointId) {
411 v8::Context::Scope contextScope(pausedContext);
412 if (!shouldContinueToCurrentLocation()) return;
413 }
414 clearContinueToLocation();
415
416 DCHECK(contextGroupId);
417 m_pausedContextGroupId = contextGroupId;
418
419 m_inspector->forEachSession(
420 contextGroupId,
421 [&pausedContext, &exception, &breakpointIds, &exceptionType, &isUncaught,
422 &scheduledOOMBreak, &breakReasons](V8InspectorSessionImpl* session) {
423 if (session->debuggerAgent()->acceptsPause(scheduledOOMBreak)) {
424 session->debuggerAgent()->didPause(
425 InspectedContext::contextId(pausedContext), exception,
426 breakpointIds, exceptionType, isUncaught, breakReasons);
427 }
428 });
429 {
430 v8::Context::Scope scope(pausedContext);
431 m_inspector->client()->runMessageLoopOnPause(contextGroupId);
432 m_pausedContextGroupId = 0;
433 }
434 m_inspector->forEachSession(contextGroupId,
435 [](V8InspectorSessionImpl* session) {
436 if (session->debuggerAgent()->enabled()) {
437 session->debuggerAgent()->clearBreakDetails();
438 session->debuggerAgent()->didContinue();
439 }
440 });
441
442 if (m_scheduledOOMBreak) m_isolate->RestoreOriginalHeapLimit();
443 m_scheduledOOMBreak = false;
444 }
445
446 namespace {
447
HeapLimitForDebugging(size_t initial_heap_limit)448 size_t HeapLimitForDebugging(size_t initial_heap_limit) {
449 const size_t kDebugHeapSizeFactor = 4;
450 size_t max_limit = std::numeric_limits<size_t>::max() / 4;
451 return std::min(max_limit, initial_heap_limit * kDebugHeapSizeFactor);
452 }
453
454 } // anonymous namespace
455
nearHeapLimitCallback(void * data,size_t current_heap_limit,size_t initial_heap_limit)456 size_t V8Debugger::nearHeapLimitCallback(void* data, size_t current_heap_limit,
457 size_t initial_heap_limit) {
458 V8Debugger* thisPtr = static_cast<V8Debugger*>(data);
459 thisPtr->m_originalHeapLimit = current_heap_limit;
460 thisPtr->m_scheduledOOMBreak = true;
461 v8::Local<v8::Context> context =
462 thisPtr->m_isolate->GetEnteredOrMicrotaskContext();
463 thisPtr->m_targetContextGroupId =
464 context.IsEmpty() ? 0 : thisPtr->m_inspector->contextGroupId(context);
465 thisPtr->m_isolate->RequestInterrupt(
466 [](v8::Isolate* isolate, void*) {
467 // There's a redundancy between setting `m_scheduledOOMBreak` and
468 // passing the reason along in `BreakRightNow`. The
469 // `m_scheduledOOMBreak` is used elsewhere, so we cannot remove it. And
470 // for being explicit, we still pass the break reason along.
471 v8::debug::BreakRightNow(
472 isolate, v8::debug::BreakReasons({v8::debug::BreakReason::kOOM}));
473 },
474 nullptr);
475 return HeapLimitForDebugging(initial_heap_limit);
476 }
477
ScriptCompiled(v8::Local<v8::debug::Script> script,bool is_live_edited,bool has_compile_error)478 void V8Debugger::ScriptCompiled(v8::Local<v8::debug::Script> script,
479 bool is_live_edited, bool has_compile_error) {
480 if (m_ignoreScriptParsedEventsCounter != 0) return;
481
482 int contextId;
483 if (!script->ContextId().To(&contextId)) return;
484
485 v8::Isolate* isolate = m_isolate;
486 V8InspectorClient* client = m_inspector->client();
487
488 m_inspector->forEachSession(
489 m_inspector->contextGroupId(contextId),
490 [isolate, &script, has_compile_error, is_live_edited,
491 client](V8InspectorSessionImpl* session) {
492 auto agent = session->debuggerAgent();
493 if (!agent->enabled()) return;
494 agent->didParseSource(
495 V8DebuggerScript::Create(isolate, script, is_live_edited, agent,
496 client),
497 !has_compile_error);
498 });
499 }
500
BreakOnInstrumentation(v8::Local<v8::Context> pausedContext,v8::debug::BreakpointId instrumentationId)501 void V8Debugger::BreakOnInstrumentation(
502 v8::Local<v8::Context> pausedContext,
503 v8::debug::BreakpointId instrumentationId) {
504 // Don't allow nested breaks.
505 if (isPaused()) return;
506
507 int contextGroupId = m_inspector->contextGroupId(pausedContext);
508 bool hasAgents = false;
509 m_inspector->forEachSession(
510 contextGroupId, [&hasAgents](V8InspectorSessionImpl* session) {
511 if (session->debuggerAgent()->acceptsPause(false /* isOOMBreak */))
512 hasAgents = true;
513 });
514 if (!hasAgents) return;
515
516 m_pausedContextGroupId = contextGroupId;
517 m_inspector->forEachSession(
518 contextGroupId, [instrumentationId](V8InspectorSessionImpl* session) {
519 if (session->debuggerAgent()->acceptsPause(false /* isOOMBreak */)) {
520 session->debuggerAgent()->didPauseOnInstrumentation(
521 instrumentationId);
522 }
523 });
524 {
525 v8::Context::Scope scope(pausedContext);
526 m_inspector->client()->runMessageLoopOnPause(contextGroupId);
527 m_pausedContextGroupId = 0;
528 }
529
530 m_inspector->forEachSession(contextGroupId,
531 [](V8InspectorSessionImpl* session) {
532 if (session->debuggerAgent()->enabled())
533 session->debuggerAgent()->didContinue();
534 });
535 }
536
BreakProgramRequested(v8::Local<v8::Context> pausedContext,const std::vector<v8::debug::BreakpointId> & break_points_hit,v8::debug::BreakReasons reasons)537 void V8Debugger::BreakProgramRequested(
538 v8::Local<v8::Context> pausedContext,
539 const std::vector<v8::debug::BreakpointId>& break_points_hit,
540 v8::debug::BreakReasons reasons) {
541 handleProgramBreak(pausedContext, v8::Local<v8::Value>(), break_points_hit,
542 reasons);
543 }
544
ExceptionThrown(v8::Local<v8::Context> pausedContext,v8::Local<v8::Value> exception,v8::Local<v8::Value> promise,bool isUncaught,v8::debug::ExceptionType exceptionType)545 void V8Debugger::ExceptionThrown(v8::Local<v8::Context> pausedContext,
546 v8::Local<v8::Value> exception,
547 v8::Local<v8::Value> promise, bool isUncaught,
548 v8::debug::ExceptionType exceptionType) {
549 std::vector<v8::debug::BreakpointId> break_points_hit;
550 handleProgramBreak(
551 pausedContext, exception, break_points_hit,
552 v8::debug::BreakReasons({v8::debug::BreakReason::kException}),
553 exceptionType, isUncaught);
554 }
555
IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,const v8::debug::Location & start,const v8::debug::Location & end)556 bool V8Debugger::IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
557 const v8::debug::Location& start,
558 const v8::debug::Location& end) {
559 int contextId;
560 if (!script->ContextId().To(&contextId)) return false;
561 bool hasAgents = false;
562 bool allBlackboxed = true;
563 String16 scriptId = String16::fromInteger(script->Id());
564 m_inspector->forEachSession(
565 m_inspector->contextGroupId(contextId),
566 [&hasAgents, &allBlackboxed, &scriptId, &start,
567 &end](V8InspectorSessionImpl* session) {
568 V8DebuggerAgentImpl* agent = session->debuggerAgent();
569 if (!agent->enabled()) return;
570 hasAgents = true;
571 allBlackboxed &= agent->isFunctionBlackboxed(scriptId, start, end);
572 });
573 return hasAgents && allBlackboxed;
574 }
575
ShouldBeSkipped(v8::Local<v8::debug::Script> script,int line,int column)576 bool V8Debugger::ShouldBeSkipped(v8::Local<v8::debug::Script> script, int line,
577 int column) {
578 int contextId;
579 if (!script->ContextId().To(&contextId)) return false;
580
581 bool hasAgents = false;
582 bool allShouldBeSkipped = true;
583 String16 scriptId = String16::fromInteger(script->Id());
584 m_inspector->forEachSession(
585 m_inspector->contextGroupId(contextId),
586 [&hasAgents, &allShouldBeSkipped, &scriptId, line,
587 column](V8InspectorSessionImpl* session) {
588 V8DebuggerAgentImpl* agent = session->debuggerAgent();
589 if (!agent->enabled()) return;
590 hasAgents = true;
591 const bool skip = agent->shouldBeSkipped(scriptId, line, column);
592 allShouldBeSkipped &= skip;
593 });
594 return hasAgents && allShouldBeSkipped;
595 }
596
AsyncEventOccurred(v8::debug::DebugAsyncActionType type,int id,bool isBlackboxed)597 void V8Debugger::AsyncEventOccurred(v8::debug::DebugAsyncActionType type,
598 int id, bool isBlackboxed) {
599 // Async task events from Promises are given misaligned pointers to prevent
600 // from overlapping with other Blink task identifiers.
601 void* task = reinterpret_cast<void*>(id * 2 + 1);
602 switch (type) {
603 case v8::debug::kDebugPromiseThen:
604 asyncTaskScheduledForStack(toStringView("Promise.then"), task, false);
605 if (!isBlackboxed) asyncTaskCandidateForStepping(task);
606 break;
607 case v8::debug::kDebugPromiseCatch:
608 asyncTaskScheduledForStack(toStringView("Promise.catch"), task, false);
609 if (!isBlackboxed) asyncTaskCandidateForStepping(task);
610 break;
611 case v8::debug::kDebugPromiseFinally:
612 asyncTaskScheduledForStack(toStringView("Promise.finally"), task, false);
613 if (!isBlackboxed) asyncTaskCandidateForStepping(task);
614 break;
615 case v8::debug::kDebugWillHandle:
616 asyncTaskStartedForStack(task);
617 asyncTaskStartedForStepping(task);
618 break;
619 case v8::debug::kDebugDidHandle:
620 asyncTaskFinishedForStack(task);
621 asyncTaskFinishedForStepping(task);
622 break;
623 case v8::debug::kDebugAwait: {
624 asyncTaskScheduledForStack(toStringView("await"), task, false, true);
625 break;
626 }
627 }
628 }
629
currentAsyncParent()630 std::shared_ptr<AsyncStackTrace> V8Debugger::currentAsyncParent() {
631 return m_currentAsyncParent.empty() ? nullptr : m_currentAsyncParent.back();
632 }
633
currentExternalParent()634 V8StackTraceId V8Debugger::currentExternalParent() {
635 return m_currentExternalParent.empty() ? V8StackTraceId()
636 : m_currentExternalParent.back();
637 }
638
getTargetScopes(v8::Local<v8::Context> context,v8::Local<v8::Value> value,ScopeTargetKind kind)639 v8::MaybeLocal<v8::Value> V8Debugger::getTargetScopes(
640 v8::Local<v8::Context> context, v8::Local<v8::Value> value,
641 ScopeTargetKind kind) {
642 v8::Local<v8::Value> scopesValue;
643 std::unique_ptr<v8::debug::ScopeIterator> iterator;
644 switch (kind) {
645 case FUNCTION:
646 iterator = v8::debug::ScopeIterator::CreateForFunction(
647 m_isolate, value.As<v8::Function>());
648 break;
649 case GENERATOR:
650 v8::Local<v8::debug::GeneratorObject> generatorObject =
651 v8::debug::GeneratorObject::Cast(value);
652 if (!generatorObject->IsSuspended()) return v8::MaybeLocal<v8::Value>();
653
654 iterator = v8::debug::ScopeIterator::CreateForGeneratorObject(
655 m_isolate, value.As<v8::Object>());
656 break;
657 }
658 if (!iterator) return v8::MaybeLocal<v8::Value>();
659 v8::Local<v8::Array> result = v8::Array::New(m_isolate);
660 if (!result->SetPrototype(context, v8::Null(m_isolate)).FromMaybe(false)) {
661 return v8::MaybeLocal<v8::Value>();
662 }
663
664 for (; !iterator->Done(); iterator->Advance()) {
665 v8::Local<v8::Object> scope = v8::Object::New(m_isolate);
666 if (!addInternalObject(context, scope, V8InternalValueType::kScope))
667 return v8::MaybeLocal<v8::Value>();
668 String16 nameSuffix = toProtocolStringWithTypeCheck(
669 m_isolate, iterator->GetFunctionDebugName());
670 String16 description;
671 if (nameSuffix.length()) nameSuffix = " (" + nameSuffix + ")";
672 switch (iterator->GetType()) {
673 case v8::debug::ScopeIterator::ScopeTypeGlobal:
674 description = "Global" + nameSuffix;
675 break;
676 case v8::debug::ScopeIterator::ScopeTypeLocal:
677 description = "Local" + nameSuffix;
678 break;
679 case v8::debug::ScopeIterator::ScopeTypeWith:
680 description = "With Block" + nameSuffix;
681 break;
682 case v8::debug::ScopeIterator::ScopeTypeClosure:
683 description = "Closure" + nameSuffix;
684 break;
685 case v8::debug::ScopeIterator::ScopeTypeCatch:
686 description = "Catch" + nameSuffix;
687 break;
688 case v8::debug::ScopeIterator::ScopeTypeBlock:
689 description = "Block" + nameSuffix;
690 break;
691 case v8::debug::ScopeIterator::ScopeTypeScript:
692 description = "Script" + nameSuffix;
693 break;
694 case v8::debug::ScopeIterator::ScopeTypeEval:
695 description = "Eval" + nameSuffix;
696 break;
697 case v8::debug::ScopeIterator::ScopeTypeModule:
698 description = "Module" + nameSuffix;
699 break;
700 case v8::debug::ScopeIterator::ScopeTypeWasmExpressionStack:
701 description = "Wasm Expression Stack" + nameSuffix;
702 break;
703 }
704 v8::Local<v8::Object> object = iterator->GetObject();
705 createDataProperty(context, scope,
706 toV8StringInternalized(m_isolate, "description"),
707 toV8String(m_isolate, description));
708 createDataProperty(context, scope,
709 toV8StringInternalized(m_isolate, "object"), object);
710 createDataProperty(context, result, result->Length(), scope);
711 }
712 if (!addInternalObject(context, result, V8InternalValueType::kScopeList))
713 return v8::MaybeLocal<v8::Value>();
714 return result;
715 }
716
functionScopes(v8::Local<v8::Context> context,v8::Local<v8::Function> function)717 v8::MaybeLocal<v8::Value> V8Debugger::functionScopes(
718 v8::Local<v8::Context> context, v8::Local<v8::Function> function) {
719 return getTargetScopes(context, function, FUNCTION);
720 }
721
generatorScopes(v8::Local<v8::Context> context,v8::Local<v8::Value> generator)722 v8::MaybeLocal<v8::Value> V8Debugger::generatorScopes(
723 v8::Local<v8::Context> context, v8::Local<v8::Value> generator) {
724 return getTargetScopes(context, generator, GENERATOR);
725 }
726
collectionsEntries(v8::Local<v8::Context> context,v8::Local<v8::Value> collection)727 v8::MaybeLocal<v8::Array> V8Debugger::collectionsEntries(
728 v8::Local<v8::Context> context, v8::Local<v8::Value> collection) {
729 v8::Isolate* isolate = context->GetIsolate();
730 v8::Local<v8::Array> entries;
731 bool isKeyValue = false;
732 if (!collection->IsObject() || !collection.As<v8::Object>()
733 ->PreviewEntries(&isKeyValue)
734 .ToLocal(&entries)) {
735 return v8::MaybeLocal<v8::Array>();
736 }
737
738 v8::Local<v8::Array> wrappedEntries = v8::Array::New(isolate);
739 CHECK(!isKeyValue || wrappedEntries->Length() % 2 == 0);
740 if (!wrappedEntries->SetPrototype(context, v8::Null(isolate))
741 .FromMaybe(false))
742 return v8::MaybeLocal<v8::Array>();
743 for (uint32_t i = 0; i < entries->Length(); i += isKeyValue ? 2 : 1) {
744 v8::Local<v8::Value> item;
745 if (!entries->Get(context, i).ToLocal(&item)) continue;
746 v8::Local<v8::Value> value;
747 if (isKeyValue && !entries->Get(context, i + 1).ToLocal(&value)) continue;
748 v8::Local<v8::Object> wrapper = v8::Object::New(isolate);
749 if (!wrapper->SetPrototype(context, v8::Null(isolate)).FromMaybe(false))
750 continue;
751 createDataProperty(
752 context, wrapper,
753 toV8StringInternalized(isolate, isKeyValue ? "key" : "value"), item);
754 if (isKeyValue) {
755 createDataProperty(context, wrapper,
756 toV8StringInternalized(isolate, "value"), value);
757 }
758 if (!addInternalObject(context, wrapper, V8InternalValueType::kEntry))
759 continue;
760 createDataProperty(context, wrappedEntries, wrappedEntries->Length(),
761 wrapper);
762 }
763 return wrappedEntries;
764 }
765
internalProperties(v8::Local<v8::Context> context,v8::Local<v8::Value> value)766 v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(
767 v8::Local<v8::Context> context, v8::Local<v8::Value> value) {
768 v8::Local<v8::Array> properties;
769 if (!v8::debug::GetInternalProperties(m_isolate, value).ToLocal(&properties))
770 return v8::MaybeLocal<v8::Array>();
771 v8::Local<v8::Array> entries;
772 if (collectionsEntries(context, value).ToLocal(&entries)) {
773 createDataProperty(context, properties, properties->Length(),
774 toV8StringInternalized(m_isolate, "[[Entries]]"));
775 createDataProperty(context, properties, properties->Length(), entries);
776 }
777 if (value->IsGeneratorObject()) {
778 v8::Local<v8::Value> scopes;
779 if (generatorScopes(context, value).ToLocal(&scopes)) {
780 createDataProperty(context, properties, properties->Length(),
781 toV8StringInternalized(m_isolate, "[[Scopes]]"));
782 createDataProperty(context, properties, properties->Length(), scopes);
783 }
784 }
785 if (value->IsFunction()) {
786 v8::Local<v8::Function> function = value.As<v8::Function>();
787 v8::Local<v8::Value> scopes;
788 if (functionScopes(context, function).ToLocal(&scopes)) {
789 createDataProperty(context, properties, properties->Length(),
790 toV8StringInternalized(m_isolate, "[[Scopes]]"));
791 createDataProperty(context, properties, properties->Length(), scopes);
792 }
793 }
794 return properties;
795 }
796
queryObjects(v8::Local<v8::Context> context,v8::Local<v8::Object> prototype)797 v8::Local<v8::Array> V8Debugger::queryObjects(v8::Local<v8::Context> context,
798 v8::Local<v8::Object> prototype) {
799 v8::Isolate* isolate = context->GetIsolate();
800 v8::PersistentValueVector<v8::Object> v8Objects(isolate);
801 MatchPrototypePredicate predicate(m_inspector, context, prototype);
802 v8::debug::QueryObjects(context, &predicate, &v8Objects);
803
804 v8::MicrotasksScope microtasksScope(isolate,
805 v8::MicrotasksScope::kDoNotRunMicrotasks);
806 v8::Local<v8::Array> resultArray = v8::Array::New(
807 m_inspector->isolate(), static_cast<int>(v8Objects.Size()));
808 for (size_t i = 0; i < v8Objects.Size(); ++i) {
809 createDataProperty(context, resultArray, static_cast<int>(i),
810 v8Objects.Get(i));
811 }
812 return resultArray;
813 }
814
createStackTrace(v8::Local<v8::StackTrace> v8StackTrace)815 std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(
816 v8::Local<v8::StackTrace> v8StackTrace) {
817 return V8StackTraceImpl::create(
818 this, v8StackTrace, V8StackTraceImpl::kDefaultMaxCallStackSizeToCapture);
819 }
820
setAsyncCallStackDepth(V8DebuggerAgentImpl * agent,int depth)821 void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) {
822 if (depth <= 0)
823 m_maxAsyncCallStackDepthMap.erase(agent);
824 else
825 m_maxAsyncCallStackDepthMap[agent] = depth;
826
827 int maxAsyncCallStackDepth = 0;
828 for (const auto& pair : m_maxAsyncCallStackDepthMap) {
829 if (pair.second > maxAsyncCallStackDepth)
830 maxAsyncCallStackDepth = pair.second;
831 }
832
833 if (m_maxAsyncCallStackDepth == maxAsyncCallStackDepth) return;
834 // TODO(dgozman): ideally, this should be per context group.
835 m_maxAsyncCallStackDepth = maxAsyncCallStackDepth;
836 m_inspector->client()->maxAsyncCallStackDepthChanged(
837 m_maxAsyncCallStackDepth);
838 if (!maxAsyncCallStackDepth) allAsyncTasksCanceled();
839 v8::debug::SetAsyncEventDelegate(m_isolate,
840 maxAsyncCallStackDepth ? this : nullptr);
841 }
842
setMaxCallStackSizeToCapture(V8RuntimeAgentImpl * agent,int size)843 void V8Debugger::setMaxCallStackSizeToCapture(V8RuntimeAgentImpl* agent,
844 int size) {
845 if (size < 0) {
846 m_maxCallStackSizeToCaptureMap.erase(agent);
847 } else {
848 m_maxCallStackSizeToCaptureMap[agent] = size;
849 }
850
851 // The following logic is a bit complicated to decipher because we
852 // want to retain backwards compatible semantics:
853 //
854 // (a) When no `Runtime` domain is enabled, we stick to the default
855 // maximum call stack size, but don't let V8 collect stack traces
856 // for uncaught exceptions.
857 // (b) When `Runtime` is enabled for at least one front-end, we compute
858 // the maximum of the requested maximum call stack sizes of all the
859 // front-ends whose `Runtime` domains are enabled (which might be 0),
860 // and ask V8 to collect stack traces for uncaught exceptions.
861 //
862 // The latter allows performance test automation infrastructure to drive
863 // browser via `Runtime` domain while still minimizing the performance
864 // overhead of having the inspector attached - see the relevant design
865 // document https://bit.ly/v8-cheaper-inspector-stack-traces for more
866 if (m_maxCallStackSizeToCaptureMap.empty()) {
867 m_maxCallStackSizeToCapture =
868 V8StackTraceImpl::kDefaultMaxCallStackSizeToCapture;
869 m_isolate->SetCaptureStackTraceForUncaughtExceptions(false);
870 } else {
871 m_maxCallStackSizeToCapture = 0;
872 for (auto const& pair : m_maxCallStackSizeToCaptureMap) {
873 if (m_maxCallStackSizeToCapture < pair.second)
874 m_maxCallStackSizeToCapture = pair.second;
875 }
876 m_isolate->SetCaptureStackTraceForUncaughtExceptions(
877 m_maxCallStackSizeToCapture > 0, m_maxCallStackSizeToCapture);
878 }
879 }
880
stackTraceFor(int contextGroupId,const V8StackTraceId & id)881 std::shared_ptr<AsyncStackTrace> V8Debugger::stackTraceFor(
882 int contextGroupId, const V8StackTraceId& id) {
883 if (debuggerIdFor(contextGroupId).pair() != id.debugger_id) return nullptr;
884 auto it = m_storedStackTraces.find(id.id);
885 if (it == m_storedStackTraces.end()) return nullptr;
886 return it->second.lock();
887 }
888
storeCurrentStackTrace(const StringView & description)889 V8StackTraceId V8Debugger::storeCurrentStackTrace(
890 const StringView& description) {
891 if (!m_maxAsyncCallStackDepth) return V8StackTraceId();
892
893 v8::HandleScope scope(m_isolate);
894 int contextGroupId = currentContextGroupId();
895 if (!contextGroupId) return V8StackTraceId();
896
897 std::shared_ptr<AsyncStackTrace> asyncStack =
898 AsyncStackTrace::capture(this, toString16(description));
899 if (!asyncStack) return V8StackTraceId();
900
901 uintptr_t id = AsyncStackTrace::store(this, asyncStack);
902
903 m_allAsyncStacks.push_back(std::move(asyncStack));
904 collectOldAsyncStacksIfNeeded();
905
906 bool shouldPause =
907 m_pauseOnAsyncCall && contextGroupId == m_targetContextGroupId;
908 if (shouldPause) {
909 m_pauseOnAsyncCall = false;
910 v8::debug::ClearStepping(m_isolate); // Cancel step into.
911 }
912 return V8StackTraceId(id, debuggerIdFor(contextGroupId).pair(), shouldPause);
913 }
914
storeStackTrace(std::shared_ptr<AsyncStackTrace> asyncStack)915 uintptr_t V8Debugger::storeStackTrace(
916 std::shared_ptr<AsyncStackTrace> asyncStack) {
917 uintptr_t id = ++m_lastStackTraceId;
918 m_storedStackTraces[id] = asyncStack;
919 return id;
920 }
921
externalAsyncTaskStarted(const V8StackTraceId & parent)922 void V8Debugger::externalAsyncTaskStarted(const V8StackTraceId& parent) {
923 if (!m_maxAsyncCallStackDepth || parent.IsInvalid()) return;
924 m_currentExternalParent.push_back(parent);
925 m_currentAsyncParent.emplace_back();
926 m_currentTasks.push_back(reinterpret_cast<void*>(parent.id));
927
928 if (!parent.should_pause) return;
929 bool didHaveBreak = hasScheduledBreakOnNextFunctionCall();
930 m_externalAsyncTaskPauseRequested = true;
931 if (didHaveBreak) return;
932 m_targetContextGroupId = currentContextGroupId();
933 v8::debug::SetBreakOnNextFunctionCall(m_isolate);
934 }
935
externalAsyncTaskFinished(const V8StackTraceId & parent)936 void V8Debugger::externalAsyncTaskFinished(const V8StackTraceId& parent) {
937 if (!m_maxAsyncCallStackDepth || m_currentExternalParent.empty()) return;
938 m_currentExternalParent.pop_back();
939 m_currentAsyncParent.pop_back();
940 DCHECK(m_currentTasks.back() == reinterpret_cast<void*>(parent.id));
941 m_currentTasks.pop_back();
942
943 if (!parent.should_pause) return;
944 m_externalAsyncTaskPauseRequested = false;
945 if (hasScheduledBreakOnNextFunctionCall()) return;
946 v8::debug::ClearBreakOnNextFunctionCall(m_isolate);
947 }
948
asyncTaskScheduled(const StringView & taskName,void * task,bool recurring)949 void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task,
950 bool recurring) {
951 asyncTaskScheduledForStack(taskName, task, recurring);
952 asyncTaskCandidateForStepping(task);
953 }
954
asyncTaskCanceled(void * task)955 void V8Debugger::asyncTaskCanceled(void* task) {
956 asyncTaskCanceledForStack(task);
957 asyncTaskCanceledForStepping(task);
958 }
959
asyncTaskStarted(void * task)960 void V8Debugger::asyncTaskStarted(void* task) {
961 asyncTaskStartedForStack(task);
962 asyncTaskStartedForStepping(task);
963 }
964
asyncTaskFinished(void * task)965 void V8Debugger::asyncTaskFinished(void* task) {
966 asyncTaskFinishedForStepping(task);
967 asyncTaskFinishedForStack(task);
968 }
969
asyncTaskScheduledForStack(const StringView & taskName,void * task,bool recurring,bool skipTopFrame)970 void V8Debugger::asyncTaskScheduledForStack(const StringView& taskName,
971 void* task, bool recurring,
972 bool skipTopFrame) {
973 if (!m_maxAsyncCallStackDepth) return;
974 v8::HandleScope scope(m_isolate);
975 std::shared_ptr<AsyncStackTrace> asyncStack =
976 AsyncStackTrace::capture(this, toString16(taskName), skipTopFrame);
977 if (asyncStack) {
978 m_asyncTaskStacks[task] = asyncStack;
979 if (recurring) m_recurringTasks.insert(task);
980 m_allAsyncStacks.push_back(std::move(asyncStack));
981 collectOldAsyncStacksIfNeeded();
982 }
983 }
984
asyncTaskCanceledForStack(void * task)985 void V8Debugger::asyncTaskCanceledForStack(void* task) {
986 if (!m_maxAsyncCallStackDepth) return;
987 m_asyncTaskStacks.erase(task);
988 m_recurringTasks.erase(task);
989 }
990
asyncTaskStartedForStack(void * task)991 void V8Debugger::asyncTaskStartedForStack(void* task) {
992 if (!m_maxAsyncCallStackDepth) return;
993 // Needs to support following order of events:
994 // - asyncTaskScheduled
995 // <-- attached here -->
996 // - asyncTaskStarted
997 // - asyncTaskCanceled <-- canceled before finished
998 // <-- async stack requested here -->
999 // - asyncTaskFinished
1000 m_currentTasks.push_back(task);
1001 AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(task);
1002 if (stackIt != m_asyncTaskStacks.end() && !stackIt->second.expired()) {
1003 std::shared_ptr<AsyncStackTrace> stack(stackIt->second);
1004 m_currentAsyncParent.push_back(stack);
1005 } else {
1006 m_currentAsyncParent.emplace_back();
1007 }
1008 m_currentExternalParent.emplace_back();
1009 }
1010
asyncTaskFinishedForStack(void * task)1011 void V8Debugger::asyncTaskFinishedForStack(void* task) {
1012 if (!m_maxAsyncCallStackDepth) return;
1013 // We could start instrumenting half way and the stack is empty.
1014 if (!m_currentTasks.size()) return;
1015 DCHECK(m_currentTasks.back() == task);
1016 m_currentTasks.pop_back();
1017
1018 m_currentAsyncParent.pop_back();
1019 m_currentExternalParent.pop_back();
1020
1021 if (m_recurringTasks.find(task) == m_recurringTasks.end()) {
1022 asyncTaskCanceledForStack(task);
1023 }
1024 }
1025
asyncTaskCandidateForStepping(void * task)1026 void V8Debugger::asyncTaskCandidateForStepping(void* task) {
1027 if (!m_pauseOnAsyncCall) return;
1028 int contextGroupId = currentContextGroupId();
1029 if (contextGroupId != m_targetContextGroupId) return;
1030 m_taskWithScheduledBreak = task;
1031 m_pauseOnAsyncCall = false;
1032 v8::debug::ClearStepping(m_isolate); // Cancel step into.
1033 }
1034
asyncTaskStartedForStepping(void * task)1035 void V8Debugger::asyncTaskStartedForStepping(void* task) {
1036 // TODO(kozyatinskiy): we should search task in async chain to support
1037 // blackboxing.
1038 if (task != m_taskWithScheduledBreak) return;
1039 bool didHaveBreak = hasScheduledBreakOnNextFunctionCall();
1040 m_taskWithScheduledBreakPauseRequested = true;
1041 if (didHaveBreak) return;
1042 m_targetContextGroupId = currentContextGroupId();
1043 v8::debug::SetBreakOnNextFunctionCall(m_isolate);
1044 }
1045
asyncTaskFinishedForStepping(void * task)1046 void V8Debugger::asyncTaskFinishedForStepping(void* task) {
1047 if (task != m_taskWithScheduledBreak) return;
1048 m_taskWithScheduledBreak = nullptr;
1049 m_taskWithScheduledBreakPauseRequested = false;
1050 if (hasScheduledBreakOnNextFunctionCall()) return;
1051 v8::debug::ClearBreakOnNextFunctionCall(m_isolate);
1052 }
1053
asyncTaskCanceledForStepping(void * task)1054 void V8Debugger::asyncTaskCanceledForStepping(void* task) {
1055 asyncTaskFinishedForStepping(task);
1056 }
1057
allAsyncTasksCanceled()1058 void V8Debugger::allAsyncTasksCanceled() {
1059 m_asyncTaskStacks.clear();
1060 m_recurringTasks.clear();
1061 m_currentAsyncParent.clear();
1062 m_currentExternalParent.clear();
1063 m_currentTasks.clear();
1064
1065 m_allAsyncStacks.clear();
1066 }
1067
muteScriptParsedEvents()1068 void V8Debugger::muteScriptParsedEvents() {
1069 ++m_ignoreScriptParsedEventsCounter;
1070 }
1071
unmuteScriptParsedEvents()1072 void V8Debugger::unmuteScriptParsedEvents() {
1073 --m_ignoreScriptParsedEventsCounter;
1074 DCHECK_GE(m_ignoreScriptParsedEventsCounter, 0);
1075 }
1076
captureStackTrace(bool fullStack)1077 std::unique_ptr<V8StackTraceImpl> V8Debugger::captureStackTrace(
1078 bool fullStack) {
1079 int contextGroupId = currentContextGroupId();
1080 if (!contextGroupId) return nullptr;
1081
1082 int stackSize = 1;
1083 if (fullStack) {
1084 stackSize = V8StackTraceImpl::kDefaultMaxCallStackSizeToCapture;
1085 } else {
1086 m_inspector->forEachSession(
1087 contextGroupId, [this, &stackSize](V8InspectorSessionImpl* session) {
1088 if (session->runtimeAgent()->enabled())
1089 stackSize = maxCallStackSizeToCapture();
1090 });
1091 }
1092 return V8StackTraceImpl::capture(this, stackSize);
1093 }
1094
currentContextGroupId()1095 int V8Debugger::currentContextGroupId() {
1096 if (!m_isolate->InContext()) return 0;
1097 v8::HandleScope handleScope(m_isolate);
1098 return m_inspector->contextGroupId(m_isolate->GetCurrentContext());
1099 }
1100
collectOldAsyncStacksIfNeeded()1101 void V8Debugger::collectOldAsyncStacksIfNeeded() {
1102 if (m_allAsyncStacks.size() <= m_maxAsyncCallStacks) return;
1103 size_t halfOfLimitRoundedUp =
1104 m_maxAsyncCallStacks / 2 + m_maxAsyncCallStacks % 2;
1105 while (m_allAsyncStacks.size() > halfOfLimitRoundedUp) {
1106 m_allAsyncStacks.pop_front();
1107 }
1108 cleanupExpiredWeakPointers(m_asyncTaskStacks);
1109 cleanupExpiredWeakPointers(m_cachedStackFrames);
1110 cleanupExpiredWeakPointers(m_storedStackTraces);
1111 for (auto it = m_recurringTasks.begin(); it != m_recurringTasks.end();) {
1112 if (m_asyncTaskStacks.find(*it) == m_asyncTaskStacks.end()) {
1113 it = m_recurringTasks.erase(it);
1114 } else {
1115 ++it;
1116 }
1117 }
1118 }
1119
symbolize(v8::Local<v8::StackFrame> v8Frame)1120 std::shared_ptr<StackFrame> V8Debugger::symbolize(
1121 v8::Local<v8::StackFrame> v8Frame) {
1122 int scriptId = v8Frame->GetScriptId();
1123 auto location = v8Frame->GetLocation();
1124 int lineNumber = location.GetLineNumber();
1125 int columnNumber = location.GetColumnNumber();
1126 CachedStackFrameKey key{scriptId, lineNumber, columnNumber};
1127 auto functionName = toProtocolString(isolate(), v8Frame->GetFunctionName());
1128 auto it = m_cachedStackFrames.find(key);
1129 if (it != m_cachedStackFrames.end() && !it->second.expired()) {
1130 auto stackFrame = it->second.lock();
1131 if (stackFrame->functionName() == functionName) {
1132 DCHECK_EQ(
1133 stackFrame->sourceURL(),
1134 toProtocolString(isolate(), v8Frame->GetScriptNameOrSourceURL()));
1135 return stackFrame;
1136 }
1137 }
1138 auto sourceURL =
1139 toProtocolString(isolate(), v8Frame->GetScriptNameOrSourceURL());
1140 auto hasSourceURLComment =
1141 v8Frame->GetScriptName() != v8Frame->GetScriptNameOrSourceURL();
1142 auto stackFrame = std::make_shared<StackFrame>(
1143 std::move(functionName), scriptId, std::move(sourceURL), lineNumber,
1144 columnNumber, hasSourceURLComment);
1145 m_cachedStackFrames.emplace(key, stackFrame);
1146 return stackFrame;
1147 }
1148
setMaxAsyncTaskStacksForTest(int limit)1149 void V8Debugger::setMaxAsyncTaskStacksForTest(int limit) {
1150 m_maxAsyncCallStacks = 0;
1151 collectOldAsyncStacksIfNeeded();
1152 m_maxAsyncCallStacks = limit;
1153 }
1154
debuggerIdFor(int contextGroupId)1155 internal::V8DebuggerId V8Debugger::debuggerIdFor(int contextGroupId) {
1156 auto it = m_contextGroupIdToDebuggerId.find(contextGroupId);
1157 if (it != m_contextGroupIdToDebuggerId.end()) return it->second;
1158 internal::V8DebuggerId debuggerId =
1159 internal::V8DebuggerId::generate(m_inspector);
1160 m_contextGroupIdToDebuggerId.insert(
1161 it, std::make_pair(contextGroupId, debuggerId));
1162 return debuggerId;
1163 }
1164
addInternalObject(v8::Local<v8::Context> context,v8::Local<v8::Object> object,V8InternalValueType type)1165 bool V8Debugger::addInternalObject(v8::Local<v8::Context> context,
1166 v8::Local<v8::Object> object,
1167 V8InternalValueType type) {
1168 int contextId = InspectedContext::contextId(context);
1169 InspectedContext* inspectedContext = m_inspector->getContext(contextId);
1170 return inspectedContext ? inspectedContext->addInternalObject(object, type)
1171 : false;
1172 }
1173
dumpAsyncTaskStacksStateForTest()1174 void V8Debugger::dumpAsyncTaskStacksStateForTest() {
1175 fprintf(stdout, "Async stacks count: %zu\n", m_allAsyncStacks.size());
1176 fprintf(stdout, "Scheduled async tasks: %zu\n", m_asyncTaskStacks.size());
1177 fprintf(stdout, "Recurring async tasks: %zu\n", m_recurringTasks.size());
1178 fprintf(stdout, "\n");
1179 }
1180
hasScheduledBreakOnNextFunctionCall() const1181 bool V8Debugger::hasScheduledBreakOnNextFunctionCall() const {
1182 return m_pauseOnNextCallRequested || m_taskWithScheduledBreakPauseRequested ||
1183 m_externalAsyncTaskPauseRequested;
1184 }
1185
1186 } // namespace v8_inspector
1187