• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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