Home | History | Annotate | Download | only in inspector
      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/debugger-script.h"
      8 #include "src/inspector/inspected-context.h"
      9 #include "src/inspector/protocol/Protocol.h"
     10 #include "src/inspector/script-breakpoint.h"
     11 #include "src/inspector/string-util.h"
     12 #include "src/inspector/v8-debugger-agent-impl.h"
     13 #include "src/inspector/v8-inspector-impl.h"
     14 #include "src/inspector/v8-internal-value-type.h"
     15 #include "src/inspector/v8-stack-trace-impl.h"
     16 #include "src/inspector/v8-value-copier.h"
     17 
     18 #include "include/v8-util.h"
     19 
     20 namespace v8_inspector {
     21 
     22 namespace {
     23 
     24 // Based on DevTools frontend measurement, with asyncCallStackDepth = 4,
     25 // average async call stack tail requires ~1 Kb. Let's reserve ~ 128 Mb
     26 // for async stacks.
     27 static const int kMaxAsyncTaskStacks = 128 * 1024;
     28 
     29 inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) {
     30   return value ? v8::True(isolate) : v8::False(isolate);
     31 }
     32 
     33 V8DebuggerAgentImpl* agentForScript(V8InspectorImpl* inspector,
     34                                     v8::Local<v8::debug::Script> script) {
     35   v8::Local<v8::Value> contextData;
     36   if (!script->ContextData().ToLocal(&contextData) || !contextData->IsInt32()) {
     37     return nullptr;
     38   }
     39   int contextId = static_cast<int>(contextData.As<v8::Int32>()->Value());
     40   int contextGroupId = inspector->contextGroupId(contextId);
     41   if (!contextGroupId) return nullptr;
     42   return inspector->enabledDebuggerAgentForGroup(contextGroupId);
     43 }
     44 
     45 v8::MaybeLocal<v8::Array> collectionsEntries(v8::Local<v8::Context> context,
     46                                              v8::Local<v8::Value> value) {
     47   v8::Isolate* isolate = context->GetIsolate();
     48   v8::Local<v8::Array> entries;
     49   bool isKeyValue = false;
     50   if (!v8::debug::EntriesPreview(isolate, value, &isKeyValue).ToLocal(&entries))
     51     return v8::MaybeLocal<v8::Array>();
     52 
     53   v8::Local<v8::Array> wrappedEntries = v8::Array::New(isolate);
     54   CHECK(!isKeyValue || wrappedEntries->Length() % 2 == 0);
     55   if (!wrappedEntries->SetPrototype(context, v8::Null(isolate))
     56            .FromMaybe(false))
     57     return v8::MaybeLocal<v8::Array>();
     58   for (uint32_t i = 0; i < entries->Length(); i += isKeyValue ? 2 : 1) {
     59     v8::Local<v8::Value> item;
     60     if (!entries->Get(context, i).ToLocal(&item)) continue;
     61     v8::Local<v8::Value> value;
     62     if (isKeyValue && !entries->Get(context, i + 1).ToLocal(&value)) continue;
     63     v8::Local<v8::Object> wrapper = v8::Object::New(isolate);
     64     if (!wrapper->SetPrototype(context, v8::Null(isolate)).FromMaybe(false))
     65       continue;
     66     createDataProperty(
     67         context, wrapper,
     68         toV8StringInternalized(isolate, isKeyValue ? "key" : "value"), item);
     69     if (isKeyValue) {
     70       createDataProperty(context, wrapper,
     71                          toV8StringInternalized(isolate, "value"), value);
     72     }
     73     createDataProperty(context, wrappedEntries, wrappedEntries->Length(),
     74                        wrapper);
     75   }
     76   if (!markArrayEntriesAsInternal(context, wrappedEntries,
     77                                   V8InternalValueType::kEntry)) {
     78     return v8::MaybeLocal<v8::Array>();
     79   }
     80   return wrappedEntries;
     81 }
     82 
     83 v8::MaybeLocal<v8::Object> buildLocation(v8::Local<v8::Context> context,
     84                                          int scriptId, int lineNumber,
     85                                          int columnNumber) {
     86   if (scriptId == v8::UnboundScript::kNoScriptId)
     87     return v8::MaybeLocal<v8::Object>();
     88   if (lineNumber == v8::Function::kLineOffsetNotFound ||
     89       columnNumber == v8::Function::kLineOffsetNotFound) {
     90     return v8::MaybeLocal<v8::Object>();
     91   }
     92   v8::Isolate* isolate = context->GetIsolate();
     93   v8::Local<v8::Object> location = v8::Object::New(isolate);
     94   if (!location->SetPrototype(context, v8::Null(isolate)).FromMaybe(false)) {
     95     return v8::MaybeLocal<v8::Object>();
     96   }
     97   if (!createDataProperty(context, location,
     98                           toV8StringInternalized(isolate, "scriptId"),
     99                           toV8String(isolate, String16::fromInteger(scriptId)))
    100            .FromMaybe(false)) {
    101     return v8::MaybeLocal<v8::Object>();
    102   }
    103   if (!createDataProperty(context, location,
    104                           toV8StringInternalized(isolate, "lineNumber"),
    105                           v8::Integer::New(isolate, lineNumber))
    106            .FromMaybe(false)) {
    107     return v8::MaybeLocal<v8::Object>();
    108   }
    109   if (!createDataProperty(context, location,
    110                           toV8StringInternalized(isolate, "columnNumber"),
    111                           v8::Integer::New(isolate, columnNumber))
    112            .FromMaybe(false)) {
    113     return v8::MaybeLocal<v8::Object>();
    114   }
    115   if (!markAsInternal(context, location, V8InternalValueType::kLocation)) {
    116     return v8::MaybeLocal<v8::Object>();
    117   }
    118   return location;
    119 }
    120 
    121 v8::MaybeLocal<v8::Object> generatorObjectLocation(
    122     v8::Local<v8::Context> context, v8::Local<v8::Value> value) {
    123   if (!value->IsGeneratorObject()) return v8::MaybeLocal<v8::Object>();
    124   v8::Local<v8::debug::GeneratorObject> generatorObject =
    125       v8::debug::GeneratorObject::Cast(value);
    126   if (!generatorObject->IsSuspended()) {
    127     v8::Local<v8::Function> func = generatorObject->Function();
    128     return buildLocation(context, func->ScriptId(), func->GetScriptLineNumber(),
    129                          func->GetScriptColumnNumber());
    130   }
    131   v8::Local<v8::debug::Script> script;
    132   if (!generatorObject->Script().ToLocal(&script))
    133     return v8::MaybeLocal<v8::Object>();
    134   v8::debug::Location suspendedLocation = generatorObject->SuspendedLocation();
    135   return buildLocation(context, script->Id(), suspendedLocation.GetLineNumber(),
    136                        suspendedLocation.GetColumnNumber());
    137 }
    138 
    139 }  // namespace
    140 
    141 static bool inLiveEditScope = false;
    142 
    143 v8::MaybeLocal<v8::Value> V8Debugger::callDebuggerMethod(
    144     const char* functionName, int argc, v8::Local<v8::Value> argv[],
    145     bool catchExceptions) {
    146   v8::MicrotasksScope microtasks(m_isolate,
    147                                  v8::MicrotasksScope::kDoNotRunMicrotasks);
    148   DCHECK(m_isolate->InContext());
    149   v8::Local<v8::Context> context = m_isolate->GetCurrentContext();
    150   v8::Local<v8::Object> debuggerScript = m_debuggerScript.Get(m_isolate);
    151   v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(
    152       debuggerScript
    153           ->Get(context, toV8StringInternalized(m_isolate, functionName))
    154           .ToLocalChecked());
    155   if (catchExceptions) {
    156     v8::TryCatch try_catch(m_isolate);
    157     return function->Call(context, debuggerScript, argc, argv);
    158   }
    159   return function->Call(context, debuggerScript, argc, argv);
    160 }
    161 
    162 V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
    163     : m_isolate(isolate),
    164       m_inspector(inspector),
    165       m_enableCount(0),
    166       m_breakpointsActivated(true),
    167       m_runningNestedMessageLoop(false),
    168       m_ignoreScriptParsedEventsCounter(0),
    169       m_maxAsyncCallStacks(kMaxAsyncTaskStacks),
    170       m_lastTaskId(0),
    171       m_maxAsyncCallStackDepth(0),
    172       m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
    173       m_wasmTranslation(isolate) {}
    174 
    175 V8Debugger::~V8Debugger() {}
    176 
    177 void V8Debugger::enable() {
    178   if (m_enableCount++) return;
    179   DCHECK(!enabled());
    180   v8::HandleScope scope(m_isolate);
    181   v8::debug::SetDebugDelegate(m_isolate, this);
    182   v8::debug::SetOutOfMemoryCallback(m_isolate, &V8Debugger::v8OOMCallback,
    183                                     this);
    184   m_debuggerContext.Reset(m_isolate, v8::debug::GetDebugContext(m_isolate));
    185   v8::debug::ChangeBreakOnException(m_isolate, v8::debug::NoBreakOnException);
    186   m_pauseOnExceptionsState = v8::debug::NoBreakOnException;
    187   compileDebuggerScript();
    188 }
    189 
    190 void V8Debugger::disable() {
    191   if (--m_enableCount) return;
    192   DCHECK(enabled());
    193   clearBreakpoints();
    194   m_debuggerScript.Reset();
    195   m_debuggerContext.Reset();
    196   allAsyncTasksCanceled();
    197   m_wasmTranslation.Clear();
    198   v8::debug::SetDebugDelegate(m_isolate, nullptr);
    199   v8::debug::SetOutOfMemoryCallback(m_isolate, nullptr, nullptr);
    200   m_isolate->RestoreOriginalHeapLimit();
    201 }
    202 
    203 bool V8Debugger::enabled() const { return !m_debuggerScript.IsEmpty(); }
    204 
    205 void V8Debugger::getCompiledScripts(
    206     int contextGroupId,
    207     std::vector<std::unique_ptr<V8DebuggerScript>>& result) {
    208   v8::HandleScope scope(m_isolate);
    209   v8::PersistentValueVector<v8::debug::Script> scripts(m_isolate);
    210   v8::debug::GetLoadedScripts(m_isolate, scripts);
    211   for (size_t i = 0; i < scripts.Size(); ++i) {
    212     v8::Local<v8::debug::Script> script = scripts.Get(i);
    213     if (!script->WasCompiled()) continue;
    214     v8::Local<v8::Value> contextData;
    215     if (!script->ContextData().ToLocal(&contextData) || !contextData->IsInt32())
    216       continue;
    217     int contextId = static_cast<int>(contextData.As<v8::Int32>()->Value());
    218     if (m_inspector->contextGroupId(contextId) != contextGroupId) continue;
    219     result.push_back(V8DebuggerScript::Create(m_isolate, script, false));
    220   }
    221 }
    222 
    223 String16 V8Debugger::setBreakpoint(const ScriptBreakpoint& breakpoint,
    224                                    int* actualLineNumber,
    225                                    int* actualColumnNumber) {
    226   v8::HandleScope scope(m_isolate);
    227   v8::Local<v8::Context> context = debuggerContext();
    228   v8::Context::Scope contextScope(context);
    229 
    230   v8::Local<v8::Object> info = v8::Object::New(m_isolate);
    231   bool success = false;
    232   success = info->Set(context, toV8StringInternalized(m_isolate, "sourceID"),
    233                       toV8String(m_isolate, breakpoint.script_id))
    234                 .FromMaybe(false);
    235   DCHECK(success);
    236   success = info->Set(context, toV8StringInternalized(m_isolate, "lineNumber"),
    237                       v8::Integer::New(m_isolate, breakpoint.line_number))
    238                 .FromMaybe(false);
    239   DCHECK(success);
    240   success =
    241       info->Set(context, toV8StringInternalized(m_isolate, "columnNumber"),
    242                 v8::Integer::New(m_isolate, breakpoint.column_number))
    243           .FromMaybe(false);
    244   DCHECK(success);
    245   success = info->Set(context, toV8StringInternalized(m_isolate, "condition"),
    246                       toV8String(m_isolate, breakpoint.condition))
    247                 .FromMaybe(false);
    248   DCHECK(success);
    249   USE(success);
    250 
    251   v8::Local<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(
    252       m_debuggerScript.Get(m_isolate)
    253           ->Get(context, toV8StringInternalized(m_isolate, "setBreakpoint"))
    254           .ToLocalChecked());
    255   v8::Local<v8::Value> breakpointId =
    256       v8::debug::Call(debuggerContext(), setBreakpointFunction, info)
    257           .ToLocalChecked();
    258   if (!breakpointId->IsString()) return "";
    259   *actualLineNumber =
    260       info->Get(context, toV8StringInternalized(m_isolate, "lineNumber"))
    261           .ToLocalChecked()
    262           ->Int32Value(context)
    263           .FromJust();
    264   *actualColumnNumber =
    265       info->Get(context, toV8StringInternalized(m_isolate, "columnNumber"))
    266           .ToLocalChecked()
    267           ->Int32Value(context)
    268           .FromJust();
    269   return toProtocolString(breakpointId.As<v8::String>());
    270 }
    271 
    272 void V8Debugger::removeBreakpoint(const String16& breakpointId) {
    273   v8::HandleScope scope(m_isolate);
    274   v8::Local<v8::Context> context = debuggerContext();
    275   v8::Context::Scope contextScope(context);
    276 
    277   v8::Local<v8::Object> info = v8::Object::New(m_isolate);
    278   bool success = false;
    279   success =
    280       info->Set(context, toV8StringInternalized(m_isolate, "breakpointId"),
    281                 toV8String(m_isolate, breakpointId))
    282           .FromMaybe(false);
    283   DCHECK(success);
    284   USE(success);
    285 
    286   v8::Local<v8::Function> removeBreakpointFunction =
    287       v8::Local<v8::Function>::Cast(
    288           m_debuggerScript.Get(m_isolate)
    289               ->Get(context,
    290                     toV8StringInternalized(m_isolate, "removeBreakpoint"))
    291               .ToLocalChecked());
    292   v8::debug::Call(debuggerContext(), removeBreakpointFunction, info)
    293       .ToLocalChecked();
    294 }
    295 
    296 void V8Debugger::clearBreakpoints() {
    297   v8::HandleScope scope(m_isolate);
    298   v8::Local<v8::Context> context = debuggerContext();
    299   v8::Context::Scope contextScope(context);
    300 
    301   v8::Local<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(
    302       m_debuggerScript.Get(m_isolate)
    303           ->Get(context, toV8StringInternalized(m_isolate, "clearBreakpoints"))
    304           .ToLocalChecked());
    305   v8::debug::Call(debuggerContext(), clearBreakpoints).ToLocalChecked();
    306 }
    307 
    308 void V8Debugger::setBreakpointsActivated(bool activated) {
    309   if (!enabled()) {
    310     UNREACHABLE();
    311     return;
    312   }
    313   v8::debug::SetBreakPointsActive(m_isolate, activated);
    314   m_breakpointsActivated = activated;
    315 }
    316 
    317 v8::debug::ExceptionBreakState V8Debugger::getPauseOnExceptionsState() {
    318   DCHECK(enabled());
    319   return m_pauseOnExceptionsState;
    320 }
    321 
    322 void V8Debugger::setPauseOnExceptionsState(
    323     v8::debug::ExceptionBreakState pauseOnExceptionsState) {
    324   DCHECK(enabled());
    325   if (m_pauseOnExceptionsState == pauseOnExceptionsState) return;
    326   v8::debug::ChangeBreakOnException(m_isolate, pauseOnExceptionsState);
    327   m_pauseOnExceptionsState = pauseOnExceptionsState;
    328 }
    329 
    330 void V8Debugger::setPauseOnNextStatement(bool pause) {
    331   if (isPaused()) return;
    332   if (pause)
    333     v8::debug::DebugBreak(m_isolate);
    334   else
    335     v8::debug::CancelDebugBreak(m_isolate);
    336 }
    337 
    338 bool V8Debugger::canBreakProgram() {
    339   if (!m_breakpointsActivated) return false;
    340   return v8::debug::HasNonBlackboxedFrameOnStack(m_isolate);
    341 }
    342 
    343 void V8Debugger::breakProgram() {
    344   // Don't allow nested breaks.
    345   if (isPaused()) return;
    346   if (!canBreakProgram()) return;
    347 
    348   v8::HandleScope scope(m_isolate);
    349   v8::Local<v8::Function> breakFunction;
    350   if (!v8::Function::New(m_isolate->GetCurrentContext(),
    351                          &V8Debugger::breakProgramCallback,
    352                          v8::External::New(m_isolate, this), 0,
    353                          v8::ConstructorBehavior::kThrow)
    354            .ToLocal(&breakFunction))
    355     return;
    356   v8::debug::Call(debuggerContext(), breakFunction).ToLocalChecked();
    357 }
    358 
    359 void V8Debugger::continueProgram() {
    360   if (isPaused()) m_inspector->client()->quitMessageLoopOnPause();
    361   m_pausedContext.Clear();
    362   m_executionState.Clear();
    363 }
    364 
    365 void V8Debugger::stepIntoStatement() {
    366   DCHECK(isPaused());
    367   DCHECK(!m_executionState.IsEmpty());
    368   v8::debug::PrepareStep(m_isolate, v8::debug::StepIn);
    369   continueProgram();
    370 }
    371 
    372 void V8Debugger::stepOverStatement() {
    373   DCHECK(isPaused());
    374   DCHECK(!m_executionState.IsEmpty());
    375   v8::debug::PrepareStep(m_isolate, v8::debug::StepNext);
    376   continueProgram();
    377 }
    378 
    379 void V8Debugger::stepOutOfFunction() {
    380   DCHECK(isPaused());
    381   DCHECK(!m_executionState.IsEmpty());
    382   v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
    383   continueProgram();
    384 }
    385 
    386 Response V8Debugger::setScriptSource(
    387     const String16& sourceID, v8::Local<v8::String> newSource, bool dryRun,
    388     Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails,
    389     JavaScriptCallFrames* newCallFrames, Maybe<bool>* stackChanged,
    390     bool* compileError) {
    391   class EnableLiveEditScope {
    392    public:
    393     explicit EnableLiveEditScope(v8::Isolate* isolate) : m_isolate(isolate) {
    394       v8::debug::SetLiveEditEnabled(m_isolate, true);
    395       inLiveEditScope = true;
    396     }
    397     ~EnableLiveEditScope() {
    398       v8::debug::SetLiveEditEnabled(m_isolate, false);
    399       inLiveEditScope = false;
    400     }
    401 
    402    private:
    403     v8::Isolate* m_isolate;
    404   };
    405 
    406   *compileError = false;
    407   DCHECK(enabled());
    408   v8::HandleScope scope(m_isolate);
    409 
    410   std::unique_ptr<v8::Context::Scope> contextScope;
    411   if (!isPaused())
    412     contextScope.reset(new v8::Context::Scope(debuggerContext()));
    413 
    414   v8::Local<v8::Value> argv[] = {toV8String(m_isolate, sourceID), newSource,
    415                                  v8Boolean(dryRun, m_isolate)};
    416 
    417   v8::Local<v8::Value> v8result;
    418   {
    419     EnableLiveEditScope enableLiveEditScope(m_isolate);
    420     v8::TryCatch tryCatch(m_isolate);
    421     tryCatch.SetVerbose(false);
    422     v8::MaybeLocal<v8::Value> maybeResult =
    423         callDebuggerMethod("liveEditScriptSource", 3, argv, false);
    424     if (tryCatch.HasCaught()) {
    425       v8::Local<v8::Message> message = tryCatch.Message();
    426       if (!message.IsEmpty())
    427         return Response::Error(toProtocolStringWithTypeCheck(message->Get()));
    428       else
    429         return Response::InternalError();
    430     }
    431     v8result = maybeResult.ToLocalChecked();
    432   }
    433   DCHECK(!v8result.IsEmpty());
    434   v8::Local<v8::Context> context = m_isolate->GetCurrentContext();
    435   v8::Local<v8::Object> resultTuple =
    436       v8result->ToObject(context).ToLocalChecked();
    437   int code = static_cast<int>(resultTuple->Get(context, 0)
    438                                   .ToLocalChecked()
    439                                   ->ToInteger(context)
    440                                   .ToLocalChecked()
    441                                   ->Value());
    442   switch (code) {
    443     case 0: {
    444       *stackChanged = resultTuple->Get(context, 1)
    445                           .ToLocalChecked()
    446                           ->BooleanValue(context)
    447                           .FromJust();
    448       // Call stack may have changed after if the edited function was on the
    449       // stack.
    450       if (!dryRun && isPaused()) {
    451         JavaScriptCallFrames frames = currentCallFrames();
    452         newCallFrames->swap(frames);
    453       }
    454       return Response::OK();
    455     }
    456     // Compile error.
    457     case 1: {
    458       *exceptionDetails =
    459           protocol::Runtime::ExceptionDetails::create()
    460               .setExceptionId(m_inspector->nextExceptionId())
    461               .setText(toProtocolStringWithTypeCheck(
    462                   resultTuple->Get(context, 2).ToLocalChecked()))
    463               .setLineNumber(static_cast<int>(resultTuple->Get(context, 3)
    464                                                   .ToLocalChecked()
    465                                                   ->ToInteger(context)
    466                                                   .ToLocalChecked()
    467                                                   ->Value()) -
    468                              1)
    469               .setColumnNumber(static_cast<int>(resultTuple->Get(context, 4)
    470                                                     .ToLocalChecked()
    471                                                     ->ToInteger(context)
    472                                                     .ToLocalChecked()
    473                                                     ->Value()) -
    474                                1)
    475               .build();
    476       *compileError = true;
    477       return Response::OK();
    478     }
    479   }
    480   return Response::InternalError();
    481 }
    482 
    483 JavaScriptCallFrames V8Debugger::currentCallFrames(int limit) {
    484   if (!isPaused()) return JavaScriptCallFrames();
    485   v8::Local<v8::Value> currentCallFramesV8;
    486   v8::Local<v8::Value> argv[] = {m_executionState,
    487                                  v8::Integer::New(m_isolate, limit)};
    488   if (!callDebuggerMethod("currentCallFrames", arraysize(argv), argv, true)
    489            .ToLocal(&currentCallFramesV8)) {
    490     return JavaScriptCallFrames();
    491   }
    492   if (!currentCallFramesV8->IsArray()) return JavaScriptCallFrames();
    493   v8::Local<v8::Array> callFramesArray = currentCallFramesV8.As<v8::Array>();
    494   JavaScriptCallFrames callFrames;
    495   for (uint32_t i = 0; i < callFramesArray->Length(); ++i) {
    496     v8::Local<v8::Value> callFrameValue;
    497     if (!callFramesArray->Get(debuggerContext(), i).ToLocal(&callFrameValue))
    498       return JavaScriptCallFrames();
    499     if (!callFrameValue->IsObject()) return JavaScriptCallFrames();
    500     v8::Local<v8::Object> callFrameObject = callFrameValue.As<v8::Object>();
    501     callFrames.push_back(JavaScriptCallFrame::create(
    502         debuggerContext(), v8::Local<v8::Object>::Cast(callFrameObject)));
    503   }
    504   return callFrames;
    505 }
    506 
    507 static V8Debugger* toV8Debugger(v8::Local<v8::Value> data) {
    508   void* p = v8::Local<v8::External>::Cast(data)->Value();
    509   return static_cast<V8Debugger*>(p);
    510 }
    511 
    512 void V8Debugger::breakProgramCallback(
    513     const v8::FunctionCallbackInfo<v8::Value>& info) {
    514   DCHECK_EQ(info.Length(), 2);
    515   V8Debugger* thisPtr = toV8Debugger(info.Data());
    516   if (!thisPtr->enabled()) return;
    517   v8::Local<v8::Context> pausedContext =
    518       thisPtr->m_isolate->GetCurrentContext();
    519   v8::Local<v8::Value> exception;
    520   v8::Local<v8::Array> hitBreakpoints;
    521   thisPtr->handleProgramBreak(pausedContext,
    522                               v8::Local<v8::Object>::Cast(info[0]), exception,
    523                               hitBreakpoints);
    524 }
    525 
    526 void V8Debugger::handleProgramBreak(v8::Local<v8::Context> pausedContext,
    527                                     v8::Local<v8::Object> executionState,
    528                                     v8::Local<v8::Value> exception,
    529                                     v8::Local<v8::Array> hitBreakpointNumbers,
    530                                     bool isPromiseRejection, bool isUncaught) {
    531   // Don't allow nested breaks.
    532   if (isPaused()) return;
    533 
    534   V8DebuggerAgentImpl* agent = m_inspector->enabledDebuggerAgentForGroup(
    535       m_inspector->contextGroupId(pausedContext));
    536   if (!agent || (agent->skipAllPauses() && !m_scheduledOOMBreak)) return;
    537 
    538   std::vector<String16> breakpointIds;
    539   if (!hitBreakpointNumbers.IsEmpty()) {
    540     breakpointIds.reserve(hitBreakpointNumbers->Length());
    541     for (uint32_t i = 0; i < hitBreakpointNumbers->Length(); i++) {
    542       v8::Local<v8::Value> hitBreakpointNumber =
    543           hitBreakpointNumbers->Get(debuggerContext(), i).ToLocalChecked();
    544       DCHECK(hitBreakpointNumber->IsInt32());
    545       breakpointIds.push_back(String16::fromInteger(
    546           hitBreakpointNumber->Int32Value(debuggerContext()).FromJust()));
    547     }
    548   }
    549 
    550   m_pausedContext = pausedContext;
    551   m_executionState = executionState;
    552   m_runningNestedMessageLoop = true;
    553   agent->didPause(InspectedContext::contextId(pausedContext), exception,
    554                   breakpointIds, isPromiseRejection, isUncaught,
    555                   m_scheduledOOMBreak);
    556   int groupId = m_inspector->contextGroupId(pausedContext);
    557   DCHECK(groupId);
    558   {
    559     v8::Context::Scope scope(pausedContext);
    560     v8::Local<v8::Context> context = m_isolate->GetCurrentContext();
    561     CHECK(!context.IsEmpty() &&
    562           context != v8::debug::GetDebugContext(m_isolate));
    563     m_inspector->client()->runMessageLoopOnPause(groupId);
    564     m_runningNestedMessageLoop = false;
    565   }
    566   // The agent may have been removed in the nested loop.
    567   agent = m_inspector->enabledDebuggerAgentForGroup(groupId);
    568   if (agent) agent->didContinue();
    569   if (m_scheduledOOMBreak) m_isolate->RestoreOriginalHeapLimit();
    570   m_scheduledOOMBreak = false;
    571   m_pausedContext.Clear();
    572   m_executionState.Clear();
    573 }
    574 
    575 void V8Debugger::v8OOMCallback(void* data) {
    576   V8Debugger* thisPtr = static_cast<V8Debugger*>(data);
    577   thisPtr->m_isolate->IncreaseHeapLimitForDebugging();
    578   thisPtr->m_scheduledOOMBreak = true;
    579   thisPtr->setPauseOnNextStatement(true);
    580 }
    581 
    582 void V8Debugger::ScriptCompiled(v8::Local<v8::debug::Script> script,
    583                                 bool has_compile_error) {
    584   V8DebuggerAgentImpl* agent = agentForScript(m_inspector, script);
    585   if (!agent) return;
    586   if (script->IsWasm()) {
    587     m_wasmTranslation.AddScript(script.As<v8::debug::WasmScript>(), agent);
    588   } else if (m_ignoreScriptParsedEventsCounter == 0) {
    589     agent->didParseSource(
    590         V8DebuggerScript::Create(m_isolate, script, inLiveEditScope),
    591         !has_compile_error);
    592   }
    593 }
    594 
    595 void V8Debugger::BreakProgramRequested(v8::Local<v8::Context> pausedContext,
    596                                        v8::Local<v8::Object> execState,
    597                                        v8::Local<v8::Value> breakPointsHit) {
    598   v8::Local<v8::Value> argv[] = {breakPointsHit};
    599   v8::Local<v8::Value> hitBreakpoints;
    600   if (!callDebuggerMethod("getBreakpointNumbers", 1, argv, true)
    601            .ToLocal(&hitBreakpoints)) {
    602     return;
    603   }
    604   DCHECK(hitBreakpoints->IsArray());
    605   handleProgramBreak(pausedContext, execState, v8::Local<v8::Value>(),
    606                      hitBreakpoints.As<v8::Array>());
    607 }
    608 
    609 void V8Debugger::ExceptionThrown(v8::Local<v8::Context> pausedContext,
    610                                  v8::Local<v8::Object> execState,
    611                                  v8::Local<v8::Value> exception,
    612                                  v8::Local<v8::Value> promise,
    613                                  bool isUncaught) {
    614   bool isPromiseRejection = promise->IsPromise();
    615   handleProgramBreak(pausedContext, execState, exception,
    616                      v8::Local<v8::Array>(), isPromiseRejection, isUncaught);
    617 }
    618 
    619 bool V8Debugger::IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
    620                                       const v8::debug::Location& start,
    621                                       const v8::debug::Location& end) {
    622   V8DebuggerAgentImpl* agent = agentForScript(m_inspector, script);
    623   if (!agent) return false;
    624   return agent->isFunctionBlackboxed(String16::fromInteger(script->Id()), start,
    625                                      end);
    626 }
    627 
    628 void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type,
    629                                       int id, int parentId) {
    630   if (!m_maxAsyncCallStackDepth) return;
    631   // Async task events from Promises are given misaligned pointers to prevent
    632   // from overlapping with other Blink task identifiers. There is a single
    633   // namespace of such ids, managed by src/js/promise.js.
    634   void* ptr = reinterpret_cast<void*>(id * 2 + 1);
    635   switch (type) {
    636     case v8::debug::kDebugPromiseCreated:
    637       asyncTaskCreated(
    638           ptr, parentId ? reinterpret_cast<void*>(parentId * 2 + 1) : nullptr);
    639       break;
    640     case v8::debug::kDebugEnqueueAsyncFunction:
    641       asyncTaskScheduled("async function", ptr, true);
    642       break;
    643     case v8::debug::kDebugEnqueuePromiseResolve:
    644       asyncTaskScheduled("Promise.resolve", ptr, true);
    645       break;
    646     case v8::debug::kDebugEnqueuePromiseReject:
    647       asyncTaskScheduled("Promise.reject", ptr, true);
    648       break;
    649     case v8::debug::kDebugPromiseCollected:
    650       asyncTaskCanceled(ptr);
    651       break;
    652     case v8::debug::kDebugWillHandle:
    653       asyncTaskStarted(ptr);
    654       break;
    655     case v8::debug::kDebugDidHandle:
    656       asyncTaskFinished(ptr);
    657       break;
    658   }
    659 }
    660 
    661 V8StackTraceImpl* V8Debugger::currentAsyncCallChain() {
    662   if (!m_currentStacks.size()) return nullptr;
    663   return m_currentStacks.back().get();
    664 }
    665 
    666 void V8Debugger::compileDebuggerScript() {
    667   if (!m_debuggerScript.IsEmpty()) {
    668     UNREACHABLE();
    669     return;
    670   }
    671 
    672   v8::HandleScope scope(m_isolate);
    673   v8::Context::Scope contextScope(debuggerContext());
    674 
    675   v8::Local<v8::String> scriptValue =
    676       v8::String::NewFromUtf8(m_isolate, DebuggerScript_js,
    677                               v8::NewStringType::kInternalized,
    678                               sizeof(DebuggerScript_js))
    679           .ToLocalChecked();
    680   v8::Local<v8::Value> value;
    681   if (!m_inspector->compileAndRunInternalScript(debuggerContext(), scriptValue)
    682            .ToLocal(&value)) {
    683     UNREACHABLE();
    684     return;
    685   }
    686   DCHECK(value->IsObject());
    687   m_debuggerScript.Reset(m_isolate, value.As<v8::Object>());
    688 }
    689 
    690 v8::Local<v8::Context> V8Debugger::debuggerContext() const {
    691   DCHECK(!m_debuggerContext.IsEmpty());
    692   return m_debuggerContext.Get(m_isolate);
    693 }
    694 
    695 v8::MaybeLocal<v8::Value> V8Debugger::getTargetScopes(
    696     v8::Local<v8::Context> context, v8::Local<v8::Value> value,
    697     ScopeTargetKind kind) {
    698   if (!enabled()) {
    699     UNREACHABLE();
    700     return v8::Local<v8::Value>::New(m_isolate, v8::Undefined(m_isolate));
    701   }
    702   v8::Local<v8::Value> argv[] = {value};
    703   v8::Local<v8::Value> scopesValue;
    704 
    705   const char* debuggerMethod = nullptr;
    706   switch (kind) {
    707     case FUNCTION:
    708       debuggerMethod = "getFunctionScopes";
    709       break;
    710     case GENERATOR:
    711       debuggerMethod = "getGeneratorScopes";
    712       break;
    713   }
    714 
    715   if (!callDebuggerMethod(debuggerMethod, 1, argv, true).ToLocal(&scopesValue))
    716     return v8::MaybeLocal<v8::Value>();
    717   v8::Local<v8::Value> copied;
    718   if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context,
    719                                     scopesValue)
    720            .ToLocal(&copied) ||
    721       !copied->IsArray())
    722     return v8::MaybeLocal<v8::Value>();
    723   if (!markAsInternal(context, v8::Local<v8::Array>::Cast(copied),
    724                       V8InternalValueType::kScopeList))
    725     return v8::MaybeLocal<v8::Value>();
    726   if (!markArrayEntriesAsInternal(context, v8::Local<v8::Array>::Cast(copied),
    727                                   V8InternalValueType::kScope))
    728     return v8::MaybeLocal<v8::Value>();
    729   return copied;
    730 }
    731 
    732 v8::MaybeLocal<v8::Value> V8Debugger::functionScopes(
    733     v8::Local<v8::Context> context, v8::Local<v8::Function> function) {
    734   return getTargetScopes(context, function, FUNCTION);
    735 }
    736 
    737 v8::MaybeLocal<v8::Value> V8Debugger::generatorScopes(
    738     v8::Local<v8::Context> context, v8::Local<v8::Value> generator) {
    739   return getTargetScopes(context, generator, GENERATOR);
    740 }
    741 
    742 v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(
    743     v8::Local<v8::Context> context, v8::Local<v8::Value> value) {
    744   v8::Local<v8::Array> properties;
    745   if (!v8::debug::GetInternalProperties(m_isolate, value).ToLocal(&properties))
    746     return v8::MaybeLocal<v8::Array>();
    747   if (value->IsFunction()) {
    748     v8::Local<v8::Function> function = value.As<v8::Function>();
    749     v8::Local<v8::Object> location;
    750     if (buildLocation(context, function->ScriptId(),
    751                       function->GetScriptLineNumber(),
    752                       function->GetScriptColumnNumber())
    753             .ToLocal(&location)) {
    754       createDataProperty(
    755           context, properties, properties->Length(),
    756           toV8StringInternalized(m_isolate, "[[FunctionLocation]]"));
    757       createDataProperty(context, properties, properties->Length(), location);
    758     }
    759     if (function->IsGeneratorFunction()) {
    760       createDataProperty(context, properties, properties->Length(),
    761                          toV8StringInternalized(m_isolate, "[[IsGenerator]]"));
    762       createDataProperty(context, properties, properties->Length(),
    763                          v8::True(m_isolate));
    764     }
    765   }
    766   v8::Local<v8::Array> entries;
    767   if (collectionsEntries(context, value).ToLocal(&entries)) {
    768     createDataProperty(context, properties, properties->Length(),
    769                        toV8StringInternalized(m_isolate, "[[Entries]]"));
    770     createDataProperty(context, properties, properties->Length(), entries);
    771   }
    772   if (value->IsGeneratorObject()) {
    773     v8::Local<v8::Object> location;
    774     if (generatorObjectLocation(context, value).ToLocal(&location)) {
    775       createDataProperty(
    776           context, properties, properties->Length(),
    777           toV8StringInternalized(m_isolate, "[[GeneratorLocation]]"));
    778       createDataProperty(context, properties, properties->Length(), location);
    779     }
    780     if (!enabled()) return properties;
    781     v8::Local<v8::Value> scopes;
    782     if (generatorScopes(context, value).ToLocal(&scopes)) {
    783       createDataProperty(context, properties, properties->Length(),
    784                          toV8StringInternalized(m_isolate, "[[Scopes]]"));
    785       createDataProperty(context, properties, properties->Length(), scopes);
    786     }
    787   }
    788   if (!enabled()) return properties;
    789   if (value->IsFunction()) {
    790     v8::Local<v8::Function> function = value.As<v8::Function>();
    791     v8::Local<v8::Value> boundFunction = function->GetBoundFunction();
    792     v8::Local<v8::Value> scopes;
    793     if (boundFunction->IsUndefined() &&
    794         functionScopes(context, function).ToLocal(&scopes)) {
    795       createDataProperty(context, properties, properties->Length(),
    796                          toV8StringInternalized(m_isolate, "[[Scopes]]"));
    797       createDataProperty(context, properties, properties->Length(), scopes);
    798     }
    799   }
    800   return properties;
    801 }
    802 
    803 std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(
    804     v8::Local<v8::StackTrace> stackTrace) {
    805   int contextGroupId =
    806       m_isolate->InContext()
    807           ? m_inspector->contextGroupId(m_isolate->GetCurrentContext())
    808           : 0;
    809   return V8StackTraceImpl::create(this, contextGroupId, stackTrace,
    810                                   V8StackTraceImpl::maxCallStackSizeToCapture);
    811 }
    812 
    813 void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) {
    814   if (depth <= 0)
    815     m_maxAsyncCallStackDepthMap.erase(agent);
    816   else
    817     m_maxAsyncCallStackDepthMap[agent] = depth;
    818 
    819   int maxAsyncCallStackDepth = 0;
    820   for (const auto& pair : m_maxAsyncCallStackDepthMap) {
    821     if (pair.second > maxAsyncCallStackDepth)
    822       maxAsyncCallStackDepth = pair.second;
    823   }
    824 
    825   if (m_maxAsyncCallStackDepth == maxAsyncCallStackDepth) return;
    826   m_maxAsyncCallStackDepth = maxAsyncCallStackDepth;
    827   if (!maxAsyncCallStackDepth) allAsyncTasksCanceled();
    828 }
    829 
    830 void V8Debugger::registerAsyncTaskIfNeeded(void* task) {
    831   if (m_taskToId.find(task) != m_taskToId.end()) return;
    832 
    833   int id = ++m_lastTaskId;
    834   m_taskToId[task] = id;
    835   m_idToTask[id] = task;
    836   if (static_cast<int>(m_idToTask.size()) > m_maxAsyncCallStacks) {
    837     void* taskToRemove = m_idToTask.begin()->second;
    838     asyncTaskCanceled(taskToRemove);
    839   }
    840 }
    841 
    842 void V8Debugger::asyncTaskCreated(void* task, void* parentTask) {
    843   if (!m_maxAsyncCallStackDepth) return;
    844   if (parentTask) m_parentTask[task] = parentTask;
    845   v8::HandleScope scope(m_isolate);
    846   // We don't need to pass context group id here because we gets this callback
    847   // from V8 for promise events only.
    848   // Passing one as maxStackSize forces no async chain for the new stack and
    849   // allows us to not grow exponentially.
    850   std::unique_ptr<V8StackTraceImpl> creationStack =
    851       V8StackTraceImpl::capture(this, 0, 1, String16());
    852   if (creationStack && !creationStack->isEmpty()) {
    853     m_asyncTaskCreationStacks[task] = std::move(creationStack);
    854     registerAsyncTaskIfNeeded(task);
    855   }
    856 }
    857 
    858 void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task,
    859                                     bool recurring) {
    860   if (!m_maxAsyncCallStackDepth) return;
    861   asyncTaskScheduled(toString16(taskName), task, recurring);
    862 }
    863 
    864 void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task,
    865                                     bool recurring) {
    866   if (!m_maxAsyncCallStackDepth) return;
    867   v8::HandleScope scope(m_isolate);
    868   int contextGroupId =
    869       m_isolate->InContext()
    870           ? m_inspector->contextGroupId(m_isolate->GetCurrentContext())
    871           : 0;
    872   std::unique_ptr<V8StackTraceImpl> chain = V8StackTraceImpl::capture(
    873       this, contextGroupId, V8StackTraceImpl::maxCallStackSizeToCapture,
    874       taskName);
    875   if (chain) {
    876     m_asyncTaskStacks[task] = std::move(chain);
    877     if (recurring) m_recurringTasks.insert(task);
    878     registerAsyncTaskIfNeeded(task);
    879   }
    880 }
    881 
    882 void V8Debugger::asyncTaskCanceled(void* task) {
    883   if (!m_maxAsyncCallStackDepth) return;
    884   m_asyncTaskStacks.erase(task);
    885   m_recurringTasks.erase(task);
    886   m_parentTask.erase(task);
    887   m_asyncTaskCreationStacks.erase(task);
    888   auto it = m_taskToId.find(task);
    889   if (it == m_taskToId.end()) return;
    890   m_idToTask.erase(it->second);
    891   m_taskToId.erase(it);
    892 }
    893 
    894 void V8Debugger::asyncTaskStarted(void* task) {
    895   if (!m_maxAsyncCallStackDepth) return;
    896   m_currentTasks.push_back(task);
    897   auto parentIt = m_parentTask.find(task);
    898   AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(
    899       parentIt == m_parentTask.end() ? task : parentIt->second);
    900   // Needs to support following order of events:
    901   // - asyncTaskScheduled
    902   //   <-- attached here -->
    903   // - asyncTaskStarted
    904   // - asyncTaskCanceled <-- canceled before finished
    905   //   <-- async stack requested here -->
    906   // - asyncTaskFinished
    907   std::unique_ptr<V8StackTraceImpl> stack;
    908   if (stackIt != m_asyncTaskStacks.end() && stackIt->second)
    909     stack = stackIt->second->cloneImpl();
    910   auto itCreation = m_asyncTaskCreationStacks.find(task);
    911   if (stack && itCreation != m_asyncTaskCreationStacks.end()) {
    912     stack->setCreation(itCreation->second->cloneImpl());
    913   }
    914   m_currentStacks.push_back(std::move(stack));
    915 }
    916 
    917 void V8Debugger::asyncTaskFinished(void* task) {
    918   if (!m_maxAsyncCallStackDepth) return;
    919   // We could start instrumenting half way and the stack is empty.
    920   if (!m_currentStacks.size()) return;
    921 
    922   DCHECK(m_currentTasks.back() == task);
    923   m_currentTasks.pop_back();
    924 
    925   m_currentStacks.pop_back();
    926   if (m_recurringTasks.find(task) == m_recurringTasks.end()) {
    927     asyncTaskCanceled(task);
    928   }
    929 }
    930 
    931 void V8Debugger::allAsyncTasksCanceled() {
    932   m_asyncTaskStacks.clear();
    933   m_recurringTasks.clear();
    934   m_currentStacks.clear();
    935   m_currentTasks.clear();
    936   m_parentTask.clear();
    937   m_asyncTaskCreationStacks.clear();
    938   m_idToTask.clear();
    939   m_taskToId.clear();
    940   m_lastTaskId = 0;
    941 }
    942 
    943 void V8Debugger::muteScriptParsedEvents() {
    944   ++m_ignoreScriptParsedEventsCounter;
    945 }
    946 
    947 void V8Debugger::unmuteScriptParsedEvents() {
    948   --m_ignoreScriptParsedEventsCounter;
    949   DCHECK_GE(m_ignoreScriptParsedEventsCounter, 0);
    950 }
    951 
    952 std::unique_ptr<V8StackTraceImpl> V8Debugger::captureStackTrace(
    953     bool fullStack) {
    954   if (!m_isolate->InContext()) return nullptr;
    955 
    956   v8::HandleScope handles(m_isolate);
    957   int contextGroupId =
    958       m_inspector->contextGroupId(m_isolate->GetCurrentContext());
    959   if (!contextGroupId) return nullptr;
    960 
    961   size_t stackSize =
    962       fullStack ? V8StackTraceImpl::maxCallStackSizeToCapture : 1;
    963   if (m_inspector->enabledRuntimeAgentForGroup(contextGroupId))
    964     stackSize = V8StackTraceImpl::maxCallStackSizeToCapture;
    965 
    966   return V8StackTraceImpl::capture(this, contextGroupId, stackSize);
    967 }
    968 
    969 }  // namespace v8_inspector
    970