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