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(¤tCallFramesV8)) { 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