v8-debugger.cc 40.8 KB
Newer Older
1 2 3 4
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
#include "src/inspector/v8-debugger.h"
6

7
#include "src/inspector/inspected-context.h"
8
#include "src/inspector/protocol/Protocol.h"
9 10 11
#include "src/inspector/string-util.h"
#include "src/inspector/v8-debugger-agent-impl.h"
#include "src/inspector/v8-inspector-impl.h"
12 13
#include "src/inspector/v8-inspector-session-impl.h"
#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 18
#include "include/v8-util.h"

19 20 21 22
namespace v8_inspector {

namespace {

23
static const int kMaxAsyncTaskStacks = 128 * 1024;
24
static const int kNoBreakpointId = 0;
25

26 27 28 29 30 31 32 33 34 35 36
template <typename Map>
void cleanupExpiredWeakPointers(Map& map) {
  for (auto it = map.begin(); it != map.end();) {
    if (it->second.expired()) {
      it = map.erase(it);
    } else {
      ++it;
    }
  }
}

37
class MatchPrototypePredicate : public v8::debug::QueryObjectPredicate {
38
 public:
39 40 41 42
  MatchPrototypePredicate(V8InspectorImpl* inspector,
                          v8::Local<v8::Context> context,
                          v8::Local<v8::Object> prototype)
      : m_inspector(inspector), m_context(context), m_prototype(prototype) {}
43 44

  bool Filter(v8::Local<v8::Object> object) override {
45 46 47
    v8::Local<v8::Context> objectContext = object->CreationContext();
    if (objectContext != m_context) return false;
    if (!m_inspector->client()->isInspectableHeapObject(object)) return false;
48
    // Get prototype chain for current object until first visited prototype.
49 50
    for (v8::Local<v8::Value> prototype = object->GetPrototype();
         prototype->IsObject();
51
         prototype = prototype.As<v8::Object>()->GetPrototype()) {
52
      if (m_prototype == prototype) return true;
53
    }
54
    return false;
55 56 57
  }

 private:
58
  V8InspectorImpl* m_inspector;
59
  v8::Local<v8::Context> m_context;
60
  v8::Local<v8::Value> m_prototype;
61
};
62 63 64 65 66 67 68
}  // namespace

V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
    : m_isolate(isolate),
      m_inspector(inspector),
      m_enableCount(0),
      m_ignoreScriptParsedEventsCounter(0),
69
      m_continueToLocationBreakpointId(kNoBreakpointId),
70
      m_maxAsyncCallStacks(kMaxAsyncTaskStacks),
71
      m_maxAsyncCallStackDepth(0),
72
      m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
73
      m_wasmTranslation(isolate) {}
74

75
V8Debugger::~V8Debugger() {
76 77 78 79
  m_isolate->RemoveCallCompletedCallback(
      &V8Debugger::terminateExecutionCompletedCallback);
  m_isolate->RemoveMicrotasksCompletedCallback(
      &V8Debugger::terminateExecutionCompletedCallback);
80
}
81 82 83 84

void V8Debugger::enable() {
  if (m_enableCount++) return;
  v8::HandleScope scope(m_isolate);
85
  v8::debug::SetDebugDelegate(m_isolate, this);
86
  m_isolate->AddNearHeapLimitCallback(&V8Debugger::nearHeapLimitCallback, this);
87 88
  v8::debug::ChangeBreakOnException(m_isolate, v8::debug::NoBreakOnException);
  m_pauseOnExceptionsState = v8::debug::NoBreakOnException;
89 90 91
}

void V8Debugger::disable() {
92 93 94 95 96 97 98 99 100 101 102 103
  if (isPaused()) {
    bool scheduledOOMBreak = m_scheduledOOMBreak;
    bool hasAgentAcceptsPause = false;
    m_inspector->forEachSession(
        m_pausedContextGroupId, [&scheduledOOMBreak, &hasAgentAcceptsPause](
                                    V8InspectorSessionImpl* session) {
          if (session->debuggerAgent()->acceptsPause(scheduledOOMBreak)) {
            hasAgentAcceptsPause = true;
          }
        });
    if (!hasAgentAcceptsPause) m_inspector->client()->quitMessageLoopOnPause();
  }
104
  if (--m_enableCount) return;
105
  clearContinueToLocation();
106
  m_taskWithScheduledBreak = nullptr;
107
  m_taskWithScheduledBreakDebuggerId = String16();
108
  m_pauseOnAsyncCall = false;
109
  m_wasmTranslation.Clear();
110
  v8::debug::SetDebugDelegate(m_isolate, nullptr);
111 112 113
  m_isolate->RemoveNearHeapLimitCallback(&V8Debugger::nearHeapLimitCallback,
                                         m_originalHeapLimit);
  m_originalHeapLimit = 0;
114 115
}

116 117 118 119
bool V8Debugger::isPausedInContextGroup(int contextGroupId) const {
  return isPaused() && m_pausedContextGroupId == contextGroupId;
}

120
bool V8Debugger::enabled() const { return m_enableCount > 0; }
121 122 123 124 125

void V8Debugger::getCompiledScripts(
    int contextGroupId,
    std::vector<std::unique_ptr<V8DebuggerScript>>& result) {
  v8::HandleScope scope(m_isolate);
126 127
  v8::PersistentValueVector<v8::debug::Script> scripts(m_isolate);
  v8::debug::GetLoadedScripts(m_isolate, scripts);
128
  for (size_t i = 0; i < scripts.Size(); ++i) {
129
    v8::Local<v8::debug::Script> script = scripts.Get(i);
130
    if (!script->WasCompiled()) continue;
131
    if (script->IsEmbedded()) {
132 133
      result.push_back(V8DebuggerScript::Create(m_isolate, script, false,
                                                m_inspector->client()));
134
      continue;
135 136 137
    }
    int contextId;
    if (!script->ContextId().To(&contextId)) continue;
138
    if (m_inspector->contextGroupId(contextId) != contextGroupId) continue;
139 140
    result.push_back(V8DebuggerScript::Create(m_isolate, script, false,
                                              m_inspector->client()));
141 142 143
  }
}

144
void V8Debugger::setBreakpointsActive(bool active) {
145
  if (!enabled()) {
146
    UNREACHABLE();
147 148
    return;
  }
149 150
  m_breakpointsActiveCount += active ? 1 : -1;
  v8::debug::SetBreakPointsActive(m_isolate, m_breakpointsActiveCount);
151 152
}

153
v8::debug::ExceptionBreakState V8Debugger::getPauseOnExceptionsState() {
154
  DCHECK(enabled());
155
  return m_pauseOnExceptionsState;
156 157 158
}

void V8Debugger::setPauseOnExceptionsState(
159
    v8::debug::ExceptionBreakState pauseOnExceptionsState) {
160
  DCHECK(enabled());
161
  if (m_pauseOnExceptionsState == pauseOnExceptionsState) return;
162
  v8::debug::ChangeBreakOnException(m_isolate, pauseOnExceptionsState);
163
  m_pauseOnExceptionsState = pauseOnExceptionsState;
164 165
}

166
void V8Debugger::setPauseOnNextCall(bool pause, int targetContextGroupId) {
167
  if (isPaused()) return;
168 169 170 171 172 173
  DCHECK(targetContextGroupId);
  if (!pause && m_targetContextGroupId &&
      m_targetContextGroupId != targetContextGroupId) {
    return;
  }
  m_targetContextGroupId = targetContextGroupId;
174
  m_breakRequested = pause;
175
  if (pause)
176
    v8::debug::SetBreakOnNextFunctionCall(m_isolate);
177
  else
178
    v8::debug::ClearBreakOnNextFunctionCall(m_isolate);
179 180 181
}

bool V8Debugger::canBreakProgram() {
182
  return !v8::debug::AllFramesOnStackAreBlackboxed(m_isolate);
183 184
}

185
void V8Debugger::breakProgram(int targetContextGroupId) {
186
  DCHECK(canBreakProgram());
187
  // Don't allow nested breaks.
188
  if (isPaused()) return;
189 190
  DCHECK(targetContextGroupId);
  m_targetContextGroupId = targetContextGroupId;
191
  v8::debug::BreakRightNow(m_isolate);
192 193
}

194 195 196 197 198 199 200 201 202 203
void V8Debugger::interruptAndBreak(int targetContextGroupId) {
  // Don't allow nested breaks.
  if (isPaused()) return;
  DCHECK(targetContextGroupId);
  m_targetContextGroupId = targetContextGroupId;
  m_isolate->RequestInterrupt(
      [](v8::Isolate* isolate, void*) { v8::debug::BreakRightNow(isolate); },
      nullptr);
}

204 205
void V8Debugger::continueProgram(int targetContextGroupId) {
  if (m_pausedContextGroupId != targetContextGroupId) return;
206 207 208
  if (isPaused()) m_inspector->client()->quitMessageLoopOnPause();
}

209 210 211 212 213 214 215 216 217 218 219 220
void V8Debugger::breakProgramOnAssert(int targetContextGroupId) {
  if (!enabled()) return;
  if (m_pauseOnExceptionsState == v8::debug::NoBreakOnException) return;
  // Don't allow nested breaks.
  if (isPaused()) return;
  if (!canBreakProgram()) return;
  DCHECK(targetContextGroupId);
  m_targetContextGroupId = targetContextGroupId;
  m_scheduledAssertBreak = true;
  v8::debug::BreakRightNow(m_isolate);
}

221 222
void V8Debugger::stepIntoStatement(int targetContextGroupId,
                                   bool breakOnAsyncCall) {
223
  DCHECK(isPaused());
224
  DCHECK(targetContextGroupId);
225
  if (asyncStepOutOfFunction(targetContextGroupId, true)) return;
226
  m_targetContextGroupId = targetContextGroupId;
227
  m_pauseOnAsyncCall = breakOnAsyncCall;
228
  v8::debug::PrepareStep(m_isolate, v8::debug::StepIn);
229
  continueProgram(targetContextGroupId);
230 231
}

232
void V8Debugger::stepOverStatement(int targetContextGroupId) {
233
  DCHECK(isPaused());
234
  DCHECK(targetContextGroupId);
235
  if (asyncStepOutOfFunction(targetContextGroupId, true)) return;
236
  m_targetContextGroupId = targetContextGroupId;
237
  v8::debug::PrepareStep(m_isolate, v8::debug::StepNext);
238
  continueProgram(targetContextGroupId);
239 240
}

241
void V8Debugger::stepOutOfFunction(int targetContextGroupId) {
242
  DCHECK(isPaused());
243
  DCHECK(targetContextGroupId);
244
  if (asyncStepOutOfFunction(targetContextGroupId, false)) return;
245
  m_targetContextGroupId = targetContextGroupId;
246
  v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
247
  continueProgram(targetContextGroupId);
248 249
}

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
bool V8Debugger::asyncStepOutOfFunction(int targetContextGroupId,
                                        bool onlyAtReturn) {
  auto iterator = v8::debug::StackTraceIterator::Create(m_isolate);
  DCHECK(!iterator->Done());
  bool atReturn = !iterator->GetReturnValue().IsEmpty();
  iterator->Advance();
  // Synchronous stack has more then one frame.
  if (!iterator->Done()) return false;
  // There is only one synchronous frame but we are not at return position and
  // user requests stepOver or stepInto.
  if (onlyAtReturn && !atReturn) return false;
  // If we are inside async function, current async parent was captured when
  // async function was suspended first time and we install that stack as
  // current before resume async function. So it represents current async
  // function.
  auto current = currentAsyncParent();
  if (!current) return false;
  // Lookup for parent async function.
  auto parent = current->parent();
  if (parent.expired()) return false;
  // Parent async stack will have suspended task id iff callee async function
  // is awaiting current async function. We can make stepOut there only in this
  // case.
  void* parentTask =
      std::shared_ptr<AsyncStackTrace>(parent)->suspendedTaskId();
  if (!parentTask) return false;
  pauseOnAsyncCall(targetContextGroupId,
                   reinterpret_cast<uintptr_t>(parentTask), String16());
  continueProgram(targetContextGroupId);
  return true;
}

282 283
void V8Debugger::pauseOnAsyncCall(int targetContextGroupId, uintptr_t task,
                                  const String16& debuggerId) {
284 285
  DCHECK(targetContextGroupId);
  m_targetContextGroupId = targetContextGroupId;
286 287

  m_taskWithScheduledBreak = reinterpret_cast<void*>(task);
288
  m_taskWithScheduledBreakDebuggerId = debuggerId;
289 290
}

291 292 293
void V8Debugger::terminateExecution(
    std::unique_ptr<TerminateExecutionCallback> callback) {
  if (m_terminateExecutionCallback) {
294 295 296 297
    if (callback) {
      callback->sendFailure(
          Response::Error("There is current termination request in progress"));
    }
298 299 300 301 302 303 304 305 306 307
    return;
  }
  m_terminateExecutionCallback = std::move(callback);
  m_isolate->AddCallCompletedCallback(
      &V8Debugger::terminateExecutionCompletedCallback);
  m_isolate->AddMicrotasksCompletedCallback(
      &V8Debugger::terminateExecutionCompletedCallback);
  m_isolate->TerminateExecution();
}

308 309 310
void V8Debugger::reportTermination() {
  if (!m_terminateExecutionCallback) return;
  m_isolate->RemoveCallCompletedCallback(
311
      &V8Debugger::terminateExecutionCompletedCallback);
312
  m_isolate->RemoveMicrotasksCompletedCallback(
313
      &V8Debugger::terminateExecutionCompletedCallback);
314 315 316 317 318 319
  m_isolate->CancelTerminateExecution();
  m_terminateExecutionCallback->sendSuccess();
  m_terminateExecutionCallback.reset();
}

void V8Debugger::terminateExecutionCompletedCallback(v8::Isolate* isolate) {
320 321 322
  V8InspectorImpl* inspector =
      static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
  V8Debugger* debugger = inspector->debugger();
323
  debugger->reportTermination();
324 325
}

326
Response V8Debugger::continueToLocation(
327
    int targetContextGroupId, V8DebuggerScript* script,
328 329
    std::unique_ptr<protocol::Debugger::Location> location,
    const String16& targetCallFrames) {
330 331 332
  DCHECK(isPaused());
  DCHECK(targetContextGroupId);
  m_targetContextGroupId = targetContextGroupId;
333 334 335 336
  v8::debug::Location v8Location(location->getLineNumber(),
                                 location->getColumnNumber(0));
  if (script->setBreakpoint(String16(), &v8Location,
                            &m_continueToLocationBreakpointId)) {
337 338 339 340 341 342
    m_continueToLocationTargetCallFrames = targetCallFrames;
    if (m_continueToLocationTargetCallFrames !=
        protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any) {
      m_continueToLocationStack = captureStackTrace(true);
      DCHECK(m_continueToLocationStack);
    }
343 344 345 346 347 348 349 350
    continueProgram(targetContextGroupId);
    // TODO(kozyatinskiy): Return actual line and column number.
    return Response::OK();
  } else {
    return Response::Error("Cannot continue to specified location");
  }
}

351 352 353 354
bool V8Debugger::shouldContinueToCurrentLocation() {
  if (m_continueToLocationTargetCallFrames ==
      protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any) {
    return true;
355
  }
356 357 358 359 360 361 362 363 364 365
  std::unique_ptr<V8StackTraceImpl> currentStack = captureStackTrace(true);
  if (m_continueToLocationTargetCallFrames ==
      protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Current) {
    return m_continueToLocationStack->isEqualIgnoringTopFrame(
        currentStack.get());
  }
  return true;
}

void V8Debugger::clearContinueToLocation() {
366 367 368
  if (m_continueToLocationBreakpointId == kNoBreakpointId) return;
  v8::debug::RemoveBreakpoint(m_isolate, m_continueToLocationBreakpointId);
  m_continueToLocationBreakpointId = kNoBreakpointId;
369 370
  m_continueToLocationTargetCallFrames = String16();
  m_continueToLocationStack.reset();
371 372
}

373 374 375
void V8Debugger::handleProgramBreak(
    v8::Local<v8::Context> pausedContext, v8::Local<v8::Value> exception,
    const std::vector<v8::debug::BreakpointId>& breakpointIds,
376
    v8::debug::ExceptionType exceptionType, bool isUncaught) {
377
  // Don't allow nested breaks.
378
  if (isPaused()) return;
379

380 381 382 383 384 385
  int contextGroupId = m_inspector->contextGroupId(pausedContext);
  if (m_targetContextGroupId && contextGroupId != m_targetContextGroupId) {
    v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
    return;
  }
  m_targetContextGroupId = 0;
386
  m_breakRequested = false;
387 388
  m_pauseOnAsyncCall = false;
  m_taskWithScheduledBreak = nullptr;
389
  m_taskWithScheduledBreakDebuggerId = String16();
390 391

  bool scheduledOOMBreak = m_scheduledOOMBreak;
392
  bool scheduledAssertBreak = m_scheduledAssertBreak;
393 394 395
  bool hasAgents = false;
  m_inspector->forEachSession(
      contextGroupId,
396 397 398
      [&scheduledOOMBreak, &hasAgents](V8InspectorSessionImpl* session) {
        if (session->debuggerAgent()->acceptsPause(scheduledOOMBreak))
          hasAgents = true;
399 400
      });
  if (!hasAgents) return;
401

402 403 404 405
  if (breakpointIds.size() == 1 &&
      breakpointIds[0] == m_continueToLocationBreakpointId) {
    v8::Context::Scope contextScope(pausedContext);
    if (!shouldContinueToCurrentLocation()) return;
406
  }
407
  clearContinueToLocation();
408

409
  DCHECK(contextGroupId);
410
  m_pausedContextGroupId = contextGroupId;
411 412

  m_inspector->forEachSession(
413
      contextGroupId, [&pausedContext, &exception, &breakpointIds,
414
                       &exceptionType, &isUncaught, &scheduledOOMBreak,
415
                       &scheduledAssertBreak](V8InspectorSessionImpl* session) {
416
        if (session->debuggerAgent()->acceptsPause(scheduledOOMBreak)) {
417 418
          session->debuggerAgent()->didPause(
              InspectedContext::contextId(pausedContext), exception,
419
              breakpointIds, exceptionType, isUncaught, scheduledOOMBreak,
420
              scheduledAssertBreak);
421 422
        }
      });
423
  {
424
    v8::Context::Scope scope(pausedContext);
425
    m_inspector->client()->runMessageLoopOnPause(contextGroupId);
426
    m_pausedContextGroupId = 0;
427
  }
428 429 430 431 432 433
  m_inspector->forEachSession(contextGroupId,
                              [](V8InspectorSessionImpl* session) {
                                if (session->debuggerAgent()->enabled())
                                  session->debuggerAgent()->didContinue();
                              });

434 435
  if (m_scheduledOOMBreak) m_isolate->RestoreOriginalHeapLimit();
  m_scheduledOOMBreak = false;
436
  m_scheduledAssertBreak = false;
437 438
}

439 440 441 442 443 444 445 446 447 448 449 450
namespace {

size_t HeapLimitForDebugging(size_t initial_heap_limit) {
  const size_t kDebugHeapSizeFactor = 4;
  size_t max_limit = std::numeric_limits<size_t>::max() / 4;
  return std::min(max_limit, initial_heap_limit * kDebugHeapSizeFactor);
}

}  // anonymous namespace

size_t V8Debugger::nearHeapLimitCallback(void* data, size_t current_heap_limit,
                                         size_t initial_heap_limit) {
451
  V8Debugger* thisPtr = static_cast<V8Debugger*>(data);
452
  thisPtr->m_originalHeapLimit = current_heap_limit;
453
  thisPtr->m_scheduledOOMBreak = true;
454 455
  v8::Local<v8::Context> context =
      thisPtr->m_isolate->GetEnteredOrMicrotaskContext();
456
  thisPtr->m_targetContextGroupId =
457
      context.IsEmpty() ? 0 : thisPtr->m_inspector->contextGroupId(context);
458 459 460
  thisPtr->m_isolate->RequestInterrupt(
      [](v8::Isolate* isolate, void*) { v8::debug::BreakRightNow(isolate); },
      nullptr);
461
  return HeapLimitForDebugging(initial_heap_limit);
462 463
}

464
void V8Debugger::ScriptCompiled(v8::Local<v8::debug::Script> script,
465
                                bool is_live_edited, bool has_compile_error) {
466 467
  int contextId;
  if (!script->ContextId().To(&contextId)) return;
468
  if (script->IsWasm() && script->SourceMappingURL().IsEmpty()) {
469 470 471 472 473 474 475 476
    WasmTranslation* wasmTranslation = &m_wasmTranslation;
    m_inspector->forEachSession(
        m_inspector->contextGroupId(contextId),
        [&script, &wasmTranslation](V8InspectorSessionImpl* session) {
          if (!session->debuggerAgent()->enabled()) return;
          wasmTranslation->AddScript(script.As<v8::debug::WasmScript>(),
                                     session->debuggerAgent());
        });
477
  } else if (m_ignoreScriptParsedEventsCounter == 0) {
478
    v8::Isolate* isolate = m_isolate;
479
    V8InspectorClient* client = m_inspector->client();
480 481
    m_inspector->forEachSession(
        m_inspector->contextGroupId(contextId),
482 483
        [&isolate, &script, &has_compile_error, &is_live_edited,
         &client](V8InspectorSessionImpl* session) {
484 485
          if (!session->debuggerAgent()->enabled()) return;
          session->debuggerAgent()->didParseSource(
486
              V8DebuggerScript::Create(isolate, script, is_live_edited, client),
487 488
              !has_compile_error);
        });
489 490 491
  }
}

492
void V8Debugger::BreakProgramRequested(
493
    v8::Local<v8::Context> pausedContext,
494 495
    const std::vector<v8::debug::BreakpointId>& break_points_hit) {
  handleProgramBreak(pausedContext, v8::Local<v8::Value>(), break_points_hit);
496 497 498 499
}

void V8Debugger::ExceptionThrown(v8::Local<v8::Context> pausedContext,
                                 v8::Local<v8::Value> exception,
500 501
                                 v8::Local<v8::Value> promise, bool isUncaught,
                                 v8::debug::ExceptionType exceptionType) {
502
  std::vector<v8::debug::BreakpointId> break_points_hit;
503 504
  handleProgramBreak(pausedContext, exception, break_points_hit, exceptionType,
                     isUncaught);
505 506
}

507 508 509
bool V8Debugger::IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
                                      const v8::debug::Location& start,
                                      const v8::debug::Location& end) {
510 511
  int contextId;
  if (!script->ContextId().To(&contextId)) return false;
512 513 514 515 516 517 518 519 520 521 522 523 524
  bool hasAgents = false;
  bool allBlackboxed = true;
  String16 scriptId = String16::fromInteger(script->Id());
  m_inspector->forEachSession(
      m_inspector->contextGroupId(contextId),
      [&hasAgents, &allBlackboxed, &scriptId, &start,
       &end](V8InspectorSessionImpl* session) {
        V8DebuggerAgentImpl* agent = session->debuggerAgent();
        if (!agent->enabled()) return;
        hasAgents = true;
        allBlackboxed &= agent->isFunctionBlackboxed(scriptId, start, end);
      });
  return hasAgents && allBlackboxed;
525 526
}

527 528
void V8Debugger::AsyncEventOccurred(v8::debug::DebugAsyncActionType type,
                                    int id, bool isBlackboxed) {
529
  // Async task events from Promises are given misaligned pointers to prevent
530 531
  // from overlapping with other Blink task identifiers.
  void* task = reinterpret_cast<void*>(id * 2 + 1);
532
  switch (type) {
533 534
    case v8::debug::kDebugPromiseThen:
      asyncTaskScheduledForStack("Promise.then", task, false);
535
      if (!isBlackboxed) asyncTaskCandidateForStepping(task, true);
536
      break;
537 538
    case v8::debug::kDebugPromiseCatch:
      asyncTaskScheduledForStack("Promise.catch", task, false);
539
      if (!isBlackboxed) asyncTaskCandidateForStepping(task, true);
540
      break;
541 542
    case v8::debug::kDebugPromiseFinally:
      asyncTaskScheduledForStack("Promise.finally", task, false);
543
      if (!isBlackboxed) asyncTaskCandidateForStepping(task, true);
544
      break;
545
    case v8::debug::kDebugWillHandle:
546 547
      asyncTaskStartedForStack(task);
      asyncTaskStartedForStepping(task);
548 549
      break;
    case v8::debug::kDebugDidHandle:
550 551
      asyncTaskFinishedForStack(task);
      asyncTaskFinishedForStepping(task);
552
      break;
553
    case v8::debug::kAsyncFunctionSuspended: {
554 555 556
      if (m_asyncTaskStacks.find(task) == m_asyncTaskStacks.end()) {
        asyncTaskScheduledForStack("async function", task, true);
      }
557 558 559 560 561
      auto stackIt = m_asyncTaskStacks.find(task);
      if (stackIt != m_asyncTaskStacks.end() && !stackIt->second.expired()) {
        std::shared_ptr<AsyncStackTrace> stack(stackIt->second);
        stack->setSuspendedTaskId(task);
      }
562
      break;
563
    }
564 565 566
    case v8::debug::kAsyncFunctionFinished:
      asyncTaskCanceledForStack(task);
      break;
567
  }
568 569
}

570 571
std::shared_ptr<AsyncStackTrace> V8Debugger::currentAsyncParent() {
  return m_currentAsyncParent.empty() ? nullptr : m_currentAsyncParent.back();
572 573
}

574 575 576 577 578
V8StackTraceId V8Debugger::currentExternalParent() {
  return m_currentExternalParent.empty() ? V8StackTraceId()
                                         : m_currentExternalParent.back();
}

579 580 581
v8::MaybeLocal<v8::Value> V8Debugger::getTargetScopes(
    v8::Local<v8::Context> context, v8::Local<v8::Value> value,
    ScopeTargetKind kind) {
582
  v8::Local<v8::Value> scopesValue;
583
  std::unique_ptr<v8::debug::ScopeIterator> iterator;
584 585
  switch (kind) {
    case FUNCTION:
586 587
      iterator = v8::debug::ScopeIterator::CreateForFunction(
          m_isolate, v8::Local<v8::Function>::Cast(value));
588 589
      break;
    case GENERATOR:
590 591 592 593 594 595
      v8::Local<v8::debug::GeneratorObject> generatorObject =
          v8::debug::GeneratorObject::Cast(value);
      if (!generatorObject->IsSuspended()) return v8::MaybeLocal<v8::Value>();

      iterator = v8::debug::ScopeIterator::CreateForGeneratorObject(
          m_isolate, v8::Local<v8::Object>::Cast(value));
596 597
      break;
  }
598
  if (!iterator) return v8::MaybeLocal<v8::Value>();
599 600
  v8::Local<v8::Array> result = v8::Array::New(m_isolate);
  if (!result->SetPrototype(context, v8::Null(m_isolate)).FromMaybe(false)) {
601
    return v8::MaybeLocal<v8::Value>();
602 603 604 605
  }

  for (; !iterator->Done(); iterator->Advance()) {
    v8::Local<v8::Object> scope = v8::Object::New(m_isolate);
606
    if (!addInternalObject(context, scope, V8InternalValueType::kScope))
607
      return v8::MaybeLocal<v8::Value>();
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
    String16 nameSuffix = toProtocolStringWithTypeCheck(
        m_isolate, iterator->GetFunctionDebugName());
    String16 description;
    if (nameSuffix.length()) nameSuffix = " (" + nameSuffix + ")";
    switch (iterator->GetType()) {
      case v8::debug::ScopeIterator::ScopeTypeGlobal:
        description = "Global" + nameSuffix;
        break;
      case v8::debug::ScopeIterator::ScopeTypeLocal:
        description = "Local" + nameSuffix;
        break;
      case v8::debug::ScopeIterator::ScopeTypeWith:
        description = "With Block" + nameSuffix;
        break;
      case v8::debug::ScopeIterator::ScopeTypeClosure:
        description = "Closure" + nameSuffix;
        break;
      case v8::debug::ScopeIterator::ScopeTypeCatch:
        description = "Catch" + nameSuffix;
        break;
      case v8::debug::ScopeIterator::ScopeTypeBlock:
        description = "Block" + nameSuffix;
        break;
      case v8::debug::ScopeIterator::ScopeTypeScript:
        description = "Script" + nameSuffix;
        break;
      case v8::debug::ScopeIterator::ScopeTypeEval:
        description = "Eval" + nameSuffix;
        break;
      case v8::debug::ScopeIterator::ScopeTypeModule:
        description = "Module" + nameSuffix;
        break;
640 641 642
    }
    v8::Local<v8::Object> object = iterator->GetObject();
    createDataProperty(context, scope,
643 644
                       toV8StringInternalized(m_isolate, "description"),
                       toV8String(m_isolate, description));
645 646 647 648
    createDataProperty(context, scope,
                       toV8StringInternalized(m_isolate, "object"), object);
    createDataProperty(context, result, result->Length(), scope);
  }
649
  if (!addInternalObject(context, result, V8InternalValueType::kScopeList))
650
    return v8::MaybeLocal<v8::Value>();
651
  return result;
652 653
}

654 655 656 657 658 659 660 661 662 663
v8::MaybeLocal<v8::Value> V8Debugger::functionScopes(
    v8::Local<v8::Context> context, v8::Local<v8::Function> function) {
  return getTargetScopes(context, function, FUNCTION);
}

v8::MaybeLocal<v8::Value> V8Debugger::generatorScopes(
    v8::Local<v8::Context> context, v8::Local<v8::Value> generator) {
  return getTargetScopes(context, generator, GENERATOR);
}

664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
v8::MaybeLocal<v8::Array> V8Debugger::collectionsEntries(
    v8::Local<v8::Context> context, v8::Local<v8::Value> value) {
  v8::Isolate* isolate = context->GetIsolate();
  v8::Local<v8::Array> entries;
  bool isKeyValue = false;
  if (!value->IsObject() ||
      !value.As<v8::Object>()->PreviewEntries(&isKeyValue).ToLocal(&entries)) {
    return v8::MaybeLocal<v8::Array>();
  }

  v8::Local<v8::Array> wrappedEntries = v8::Array::New(isolate);
  CHECK(!isKeyValue || wrappedEntries->Length() % 2 == 0);
  if (!wrappedEntries->SetPrototype(context, v8::Null(isolate))
           .FromMaybe(false))
    return v8::MaybeLocal<v8::Array>();
  for (uint32_t i = 0; i < entries->Length(); i += isKeyValue ? 2 : 1) {
    v8::Local<v8::Value> item;
    if (!entries->Get(context, i).ToLocal(&item)) continue;
    v8::Local<v8::Value> value;
    if (isKeyValue && !entries->Get(context, i + 1).ToLocal(&value)) continue;
    v8::Local<v8::Object> wrapper = v8::Object::New(isolate);
    if (!wrapper->SetPrototype(context, v8::Null(isolate)).FromMaybe(false))
      continue;
    createDataProperty(
        context, wrapper,
        toV8StringInternalized(isolate, isKeyValue ? "key" : "value"), item);
    if (isKeyValue) {
      createDataProperty(context, wrapper,
                         toV8StringInternalized(isolate, "value"), value);
    }
694
    if (!addInternalObject(context, wrapper, V8InternalValueType::kEntry))
695 696 697 698 699 700 701
      continue;
    createDataProperty(context, wrappedEntries, wrappedEntries->Length(),
                       wrapper);
  }
  return wrappedEntries;
}

702 703 704
v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(
    v8::Local<v8::Context> context, v8::Local<v8::Value> value) {
  v8::Local<v8::Array> properties;
705
  if (!v8::debug::GetInternalProperties(m_isolate, value).ToLocal(&properties))
706
    return v8::MaybeLocal<v8::Array>();
707 708 709 710 711
  v8::Local<v8::Array> entries;
  if (collectionsEntries(context, value).ToLocal(&entries)) {
    createDataProperty(context, properties, properties->Length(),
                       toV8StringInternalized(m_isolate, "[[Entries]]"));
    createDataProperty(context, properties, properties->Length(), entries);
712 713
  }
  if (value->IsGeneratorObject()) {
714 715 716 717 718 719
    v8::Local<v8::Value> scopes;
    if (generatorScopes(context, value).ToLocal(&scopes)) {
      createDataProperty(context, properties, properties->Length(),
                         toV8StringInternalized(m_isolate, "[[Scopes]]"));
      createDataProperty(context, properties, properties->Length(), scopes);
    }
720 721 722 723
  }
  if (value->IsFunction()) {
    v8::Local<v8::Function> function = value.As<v8::Function>();
    v8::Local<v8::Value> scopes;
724
    if (functionScopes(context, function).ToLocal(&scopes)) {
725 726 727 728 729 730 731 732
      createDataProperty(context, properties, properties->Length(),
                         toV8StringInternalized(m_isolate, "[[Scopes]]"));
      createDataProperty(context, properties, properties->Length(), scopes);
    }
  }
  return properties;
}

733 734
v8::Local<v8::Array> V8Debugger::queryObjects(v8::Local<v8::Context> context,
                                              v8::Local<v8::Object> prototype) {
735 736
  v8::Isolate* isolate = context->GetIsolate();
  v8::PersistentValueVector<v8::Object> v8Objects(isolate);
737
  MatchPrototypePredicate predicate(m_inspector, context, prototype);
738 739 740 741 742 743 744 745 746 747 748 749 750
  v8::debug::QueryObjects(context, &predicate, &v8Objects);

  v8::MicrotasksScope microtasksScope(isolate,
                                      v8::MicrotasksScope::kDoNotRunMicrotasks);
  v8::Local<v8::Array> resultArray = v8::Array::New(
      m_inspector->isolate(), static_cast<int>(v8Objects.Size()));
  for (size_t i = 0; i < v8Objects.Size(); ++i) {
    createDataProperty(context, resultArray, static_cast<int>(i),
                       v8Objects.Get(i));
  }
  return resultArray;
}

751
std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(
752 753
    v8::Local<v8::StackTrace> v8StackTrace) {
  return V8StackTraceImpl::create(this, currentContextGroupId(), v8StackTrace,
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
                                  V8StackTraceImpl::maxCallStackSizeToCapture);
}

void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) {
  if (depth <= 0)
    m_maxAsyncCallStackDepthMap.erase(agent);
  else
    m_maxAsyncCallStackDepthMap[agent] = depth;

  int maxAsyncCallStackDepth = 0;
  for (const auto& pair : m_maxAsyncCallStackDepthMap) {
    if (pair.second > maxAsyncCallStackDepth)
      maxAsyncCallStackDepth = pair.second;
  }

  if (m_maxAsyncCallStackDepth == maxAsyncCallStackDepth) return;
770
  // TODO(dgozman): ideally, this should be per context group.
771
  m_maxAsyncCallStackDepth = maxAsyncCallStackDepth;
772 773
  m_inspector->client()->maxAsyncCallStackDepthChanged(
      m_maxAsyncCallStackDepth);
774
  if (!maxAsyncCallStackDepth) allAsyncTasksCanceled();
775 776
  v8::debug::SetAsyncEventDelegate(m_isolate,
                                   maxAsyncCallStackDepth ? this : nullptr);
777 778
}

779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799
std::shared_ptr<AsyncStackTrace> V8Debugger::stackTraceFor(
    int contextGroupId, const V8StackTraceId& id) {
  if (debuggerIdFor(contextGroupId) != id.debugger_id) return nullptr;
  auto it = m_storedStackTraces.find(id.id);
  if (it == m_storedStackTraces.end()) return nullptr;
  return it->second.lock();
}

V8StackTraceId V8Debugger::storeCurrentStackTrace(
    const StringView& description) {
  if (!m_maxAsyncCallStackDepth) return V8StackTraceId();

  v8::HandleScope scope(m_isolate);
  int contextGroupId = currentContextGroupId();
  if (!contextGroupId) return V8StackTraceId();

  std::shared_ptr<AsyncStackTrace> asyncStack =
      AsyncStackTrace::capture(this, contextGroupId, toString16(description),
                               V8StackTraceImpl::maxCallStackSizeToCapture);
  if (!asyncStack) return V8StackTraceId();

800 801
  uintptr_t id = AsyncStackTrace::store(this, asyncStack);

802 803 804 805
  m_allAsyncStacks.push_back(std::move(asyncStack));
  ++m_asyncStacksCount;
  collectOldAsyncStacksIfNeeded();

806
  asyncTaskCandidateForStepping(reinterpret_cast<void*>(id), false);
807

808 809 810
  return V8StackTraceId(id, debuggerIdFor(contextGroupId));
}

811 812 813 814 815 816 817
uintptr_t V8Debugger::storeStackTrace(
    std::shared_ptr<AsyncStackTrace> asyncStack) {
  uintptr_t id = ++m_lastStackTraceId;
  m_storedStackTraces[id] = asyncStack;
  return id;
}

818 819 820 821 822
void V8Debugger::externalAsyncTaskStarted(const V8StackTraceId& parent) {
  if (!m_maxAsyncCallStackDepth || parent.IsInvalid()) return;
  m_currentExternalParent.push_back(parent);
  m_currentAsyncParent.emplace_back();
  m_currentTasks.push_back(reinterpret_cast<void*>(parent.id));
823 824 825 826 827 828

  if (m_breakRequested) return;
  if (!m_taskWithScheduledBreakDebuggerId.isEmpty() &&
      reinterpret_cast<uintptr_t>(m_taskWithScheduledBreak) == parent.id &&
      m_taskWithScheduledBreakDebuggerId ==
          debuggerIdToString(parent.debugger_id)) {
829
    v8::debug::SetBreakOnNextFunctionCall(m_isolate);
830
  }
831 832 833 834 835 836 837 838
}

void V8Debugger::externalAsyncTaskFinished(const V8StackTraceId& parent) {
  if (!m_maxAsyncCallStackDepth || m_currentExternalParent.empty()) return;
  m_currentExternalParent.pop_back();
  m_currentAsyncParent.pop_back();
  DCHECK(m_currentTasks.back() == reinterpret_cast<void*>(parent.id));
  m_currentTasks.pop_back();
839 840 841 842 843 844 845 846 847 848

  if (m_taskWithScheduledBreakDebuggerId.isEmpty() ||
      reinterpret_cast<uintptr_t>(m_taskWithScheduledBreak) != parent.id ||
      m_taskWithScheduledBreakDebuggerId !=
          debuggerIdToString(parent.debugger_id)) {
    return;
  }
  m_taskWithScheduledBreak = nullptr;
  m_taskWithScheduledBreakDebuggerId = String16();
  if (m_breakRequested) return;
849
  v8::debug::ClearBreakOnNextFunctionCall(m_isolate);
850 851
}

852 853
void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task,
                                    bool recurring) {
854
  asyncTaskScheduledForStack(toString16(taskName), task, recurring);
855
  asyncTaskCandidateForStepping(task, true);
856 857
}

858 859 860 861 862 863 864 865 866 867 868 869
void V8Debugger::asyncTaskCanceled(void* task) {
  asyncTaskCanceledForStack(task);
  asyncTaskCanceledForStepping(task);
}

void V8Debugger::asyncTaskStarted(void* task) {
  asyncTaskStartedForStack(task);
  asyncTaskStartedForStepping(task);
}

void V8Debugger::asyncTaskFinished(void* task) {
  asyncTaskFinishedForStepping(task);
870
  asyncTaskFinishedForStack(task);
871 872 873 874
}

void V8Debugger::asyncTaskScheduledForStack(const String16& taskName,
                                            void* task, bool recurring) {
875 876
  if (!m_maxAsyncCallStackDepth) return;
  v8::HandleScope scope(m_isolate);
877 878 879 880 881
  std::shared_ptr<AsyncStackTrace> asyncStack =
      AsyncStackTrace::capture(this, currentContextGroupId(), taskName,
                               V8StackTraceImpl::maxCallStackSizeToCapture);
  if (asyncStack) {
    m_asyncTaskStacks[task] = asyncStack;
882
    if (recurring) m_recurringTasks.insert(task);
883
    m_allAsyncStacks.push_back(std::move(asyncStack));
884 885
    ++m_asyncStacksCount;
    collectOldAsyncStacksIfNeeded();
886 887 888
  }
}

889
void V8Debugger::asyncTaskCanceledForStack(void* task) {
890 891 892 893 894
  if (!m_maxAsyncCallStackDepth) return;
  m_asyncTaskStacks.erase(task);
  m_recurringTasks.erase(task);
}

895
void V8Debugger::asyncTaskStartedForStack(void* task) {
896 897 898 899 900 901 902 903
  if (!m_maxAsyncCallStackDepth) return;
  // Needs to support following order of events:
  // - asyncTaskScheduled
  //   <-- attached here -->
  // - asyncTaskStarted
  // - asyncTaskCanceled <-- canceled before finished
  //   <-- async stack requested here -->
  // - asyncTaskFinished
904
  m_currentTasks.push_back(task);
905
  AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(task);
906 907 908 909
  if (stackIt != m_asyncTaskStacks.end() && !stackIt->second.expired()) {
    std::shared_ptr<AsyncStackTrace> stack(stackIt->second);
    stack->setSuspendedTaskId(nullptr);
    m_currentAsyncParent.push_back(stack);
910 911 912
  } else {
    m_currentAsyncParent.emplace_back();
  }
913
  m_currentExternalParent.emplace_back();
914 915
}

916
void V8Debugger::asyncTaskFinishedForStack(void* task) {
917 918
  if (!m_maxAsyncCallStackDepth) return;
  // We could start instrumenting half way and the stack is empty.
919
  if (!m_currentTasks.size()) return;
920 921 922
  DCHECK(m_currentTasks.back() == task);
  m_currentTasks.pop_back();

923
  m_currentAsyncParent.pop_back();
924
  m_currentExternalParent.pop_back();
925

926
  if (m_recurringTasks.find(task) == m_recurringTasks.end()) {
927
    asyncTaskCanceledForStack(task);
928
  }
929 930
}

931
void V8Debugger::asyncTaskCandidateForStepping(void* task, bool isLocal) {
932
  if (!m_pauseOnAsyncCall) return;
933 934
  int contextGroupId = currentContextGroupId();
  if (contextGroupId != m_targetContextGroupId) return;
935 936 937 938 939 940 941 942 943
  if (isLocal) {
    m_scheduledAsyncCall = v8_inspector::V8StackTraceId(
        reinterpret_cast<uintptr_t>(task), std::make_pair(0, 0));
  } else {
    m_scheduledAsyncCall = v8_inspector::V8StackTraceId(
        reinterpret_cast<uintptr_t>(task), debuggerIdFor(contextGroupId));
  }
  breakProgram(m_targetContextGroupId);
  m_scheduledAsyncCall = v8_inspector::V8StackTraceId();
944 945 946 947
}

void V8Debugger::asyncTaskStartedForStepping(void* task) {
  if (m_breakRequested) return;
948 949
  // TODO(kozyatinskiy): we should search task in async chain to support
  // blackboxing.
950 951
  if (m_taskWithScheduledBreakDebuggerId.isEmpty() &&
      task == m_taskWithScheduledBreak) {
952
    v8::debug::SetBreakOnNextFunctionCall(m_isolate);
953
  }
954 955 956
}

void V8Debugger::asyncTaskFinishedForStepping(void* task) {
957 958 959 960
  if (!m_taskWithScheduledBreakDebuggerId.isEmpty() ||
      task != m_taskWithScheduledBreak) {
    return;
  }
961 962
  m_taskWithScheduledBreak = nullptr;
  if (m_breakRequested) return;
963
  v8::debug::ClearBreakOnNextFunctionCall(m_isolate);
964 965 966
}

void V8Debugger::asyncTaskCanceledForStepping(void* task) {
967 968 969
  if (!m_taskWithScheduledBreakDebuggerId.isEmpty() ||
      task != m_taskWithScheduledBreak)
    return;
970 971 972
  m_taskWithScheduledBreak = nullptr;
}

973 974 975
void V8Debugger::allAsyncTasksCanceled() {
  m_asyncTaskStacks.clear();
  m_recurringTasks.clear();
976
  m_currentAsyncParent.clear();
977
  m_currentExternalParent.clear();
978
  m_currentTasks.clear();
979

980
  m_framesCache.clear();
981 982
  m_allAsyncStacks.clear();
  m_asyncStacksCount = 0;
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998
}

void V8Debugger::muteScriptParsedEvents() {
  ++m_ignoreScriptParsedEventsCounter;
}

void V8Debugger::unmuteScriptParsedEvents() {
  --m_ignoreScriptParsedEventsCounter;
  DCHECK_GE(m_ignoreScriptParsedEventsCounter, 0);
}

std::unique_ptr<V8StackTraceImpl> V8Debugger::captureStackTrace(
    bool fullStack) {
  if (!m_isolate->InContext()) return nullptr;

  v8::HandleScope handles(m_isolate);
999
  int contextGroupId = currentContextGroupId();
1000 1001
  if (!contextGroupId) return nullptr;

1002
  int stackSize = 1;
1003
  if (fullStack) {
1004
    stackSize = V8StackTraceImpl::maxCallStackSizeToCapture;
1005 1006 1007 1008 1009 1010
  } else {
    m_inspector->forEachSession(
        contextGroupId, [&stackSize](V8InspectorSessionImpl* session) {
          if (session->runtimeAgent()->enabled())
            stackSize = V8StackTraceImpl::maxCallStackSizeToCapture;
        });
1011
  }
1012 1013 1014
  return V8StackTraceImpl::capture(this, contextGroupId, stackSize);
}

1015 1016 1017 1018 1019
int V8Debugger::currentContextGroupId() {
  if (!m_isolate->InContext()) return 0;
  return m_inspector->contextGroupId(m_isolate->GetCurrentContext());
}

1020 1021 1022 1023 1024 1025 1026 1027
void V8Debugger::collectOldAsyncStacksIfNeeded() {
  if (m_asyncStacksCount <= m_maxAsyncCallStacks) return;
  int halfOfLimitRoundedUp =
      m_maxAsyncCallStacks / 2 + m_maxAsyncCallStacks % 2;
  while (m_asyncStacksCount > halfOfLimitRoundedUp) {
    m_allAsyncStacks.pop_front();
    --m_asyncStacksCount;
  }
1028
  cleanupExpiredWeakPointers(m_asyncTaskStacks);
1029
  cleanupExpiredWeakPointers(m_storedStackTraces);
1030 1031 1032 1033 1034 1035
  for (auto it = m_recurringTasks.begin(); it != m_recurringTasks.end();) {
    if (m_asyncTaskStacks.find(*it) == m_asyncTaskStacks.end()) {
      it = m_recurringTasks.erase(it);
    } else {
      ++it;
    }
1036
  }
1037
  cleanupExpiredWeakPointers(m_framesCache);
1038 1039
}

1040 1041 1042 1043 1044 1045 1046 1047
std::shared_ptr<StackFrame> V8Debugger::symbolize(
    v8::Local<v8::StackFrame> v8Frame) {
  auto it = m_framesCache.end();
  int frameId = 0;
  if (m_maxAsyncCallStackDepth) {
    frameId = v8::debug::GetStackFrameId(v8Frame);
    it = m_framesCache.find(frameId);
  }
1048 1049 1050
  if (it != m_framesCache.end() && !it->second.expired()) {
    return std::shared_ptr<StackFrame>(it->second);
  }
1051
  std::shared_ptr<StackFrame> frame(new StackFrame(isolate(), v8Frame));
1052 1053 1054 1055 1056 1057 1058 1059 1060
  // TODO(clemensh): Figure out a way to do this translation only right before
  // sending the stack trace over wire.
  if (v8Frame->IsWasm()) frame->translate(&m_wasmTranslation);
  if (m_maxAsyncCallStackDepth) {
    m_framesCache[frameId] = frame;
  }
  return frame;
}

1061 1062 1063 1064 1065 1066
void V8Debugger::setMaxAsyncTaskStacksForTest(int limit) {
  m_maxAsyncCallStacks = 0;
  collectOldAsyncStacksIfNeeded();
  m_maxAsyncCallStacks = limit;
}

1067 1068 1069 1070 1071 1072
std::pair<int64_t, int64_t> V8Debugger::debuggerIdFor(int contextGroupId) {
  auto it = m_contextGroupIdToDebuggerId.find(contextGroupId);
  if (it != m_contextGroupIdToDebuggerId.end()) return it->second;
  std::pair<int64_t, int64_t> debuggerId(
      v8::debug::GetNextRandomInt64(m_isolate),
      v8::debug::GetNextRandomInt64(m_isolate));
1073
  if (!debuggerId.first && !debuggerId.second) ++debuggerId.first;
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
  m_contextGroupIdToDebuggerId.insert(
      it, std::make_pair(contextGroupId, debuggerId));
  m_serializedDebuggerIdToDebuggerId.insert(
      std::make_pair(debuggerIdToString(debuggerId), debuggerId));
  return debuggerId;
}

std::pair<int64_t, int64_t> V8Debugger::debuggerIdFor(
    const String16& serializedDebuggerId) {
  auto it = m_serializedDebuggerIdToDebuggerId.find(serializedDebuggerId);
  if (it != m_serializedDebuggerIdToDebuggerId.end()) return it->second;
  return std::make_pair(0, 0);
}

1088 1089 1090
bool V8Debugger::addInternalObject(v8::Local<v8::Context> context,
                                   v8::Local<v8::Object> object,
                                   V8InternalValueType type) {
1091 1092 1093 1094
  int contextId = InspectedContext::contextId(context);
  InspectedContext* inspectedContext = m_inspector->getContext(contextId);
  return inspectedContext ? inspectedContext->addInternalObject(object, type)
                          : false;
1095 1096
}

1097 1098 1099 1100 1101 1102 1103
void V8Debugger::dumpAsyncTaskStacksStateForTest() {
  fprintf(stdout, "Async stacks count: %d\n", m_asyncStacksCount);
  fprintf(stdout, "Scheduled async tasks: %zu\n", m_asyncTaskStacks.size());
  fprintf(stdout, "Recurring async tasks: %zu\n", m_recurringTasks.size());
  fprintf(stdout, "\n");
}

1104
}  // namespace v8_inspector