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