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