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