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