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