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