1// Copyright 2015 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-agent-impl.h" 6 7#include <algorithm> 8 9#include "../../third_party/inspector_protocol/crdtp/json.h" 10#include "include/v8-context.h" 11#include "include/v8-function.h" 12#include "include/v8-inspector.h" 13#include "include/v8-microtask-queue.h" 14#include "src/base/safe_conversions.h" 15#include "src/debug/debug-interface.h" 16#include "src/inspector/injected-script.h" 17#include "src/inspector/inspected-context.h" 18#include "src/inspector/protocol/Debugger.h" 19#include "src/inspector/protocol/Protocol.h" 20#include "src/inspector/remote-object-id.h" 21#include "src/inspector/search-util.h" 22#include "src/inspector/string-util.h" 23#include "src/inspector/v8-debugger-script.h" 24#include "src/inspector/v8-debugger.h" 25#include "src/inspector/v8-inspector-impl.h" 26#include "src/inspector/v8-inspector-session-impl.h" 27#include "src/inspector/v8-regex.h" 28#include "src/inspector/v8-runtime-agent-impl.h" 29#include "src/inspector/v8-stack-trace-impl.h" 30#include "src/inspector/v8-value-utils.h" 31 32namespace v8_inspector { 33 34using protocol::Array; 35using protocol::Maybe; 36using protocol::Debugger::BreakpointId; 37using protocol::Debugger::CallFrame; 38using protocol::Debugger::Scope; 39using protocol::Runtime::ExceptionDetails; 40using protocol::Runtime::RemoteObject; 41using protocol::Runtime::ScriptId; 42 43namespace InstrumentationEnum = 44 protocol::Debugger::SetInstrumentationBreakpoint::InstrumentationEnum; 45 46namespace DebuggerAgentState { 47static const char pauseOnExceptionsState[] = "pauseOnExceptionsState"; 48static const char asyncCallStackDepth[] = "asyncCallStackDepth"; 49static const char blackboxPattern[] = "blackboxPattern"; 50static const char debuggerEnabled[] = "debuggerEnabled"; 51static const char skipAllPauses[] = "skipAllPauses"; 52 53static const char breakpointsByRegex[] = "breakpointsByRegex"; 54static const char breakpointsByUrl[] = "breakpointsByUrl"; 55static const char breakpointsByScriptHash[] = "breakpointsByScriptHash"; 56static const char breakpointHints[] = "breakpointHints"; 57static const char instrumentationBreakpoints[] = "instrumentationBreakpoints"; 58 59} // namespace DebuggerAgentState 60 61static const char kBacktraceObjectGroup[] = "backtrace"; 62static const char kDebuggerNotEnabled[] = "Debugger agent is not enabled"; 63static const char kDebuggerNotPaused[] = 64 "Can only perform operation while paused."; 65 66static const size_t kBreakpointHintMaxLength = 128; 67static const intptr_t kBreakpointHintMaxSearchOffset = 80 * 10; 68// Limit the number of breakpoints returned, as we otherwise may exceed 69// the maximum length of a message in mojo (see https://crbug.com/1105172). 70static const size_t kMaxNumBreakpoints = 1000; 71 72#if V8_ENABLE_WEBASSEMBLY 73// TODO(1099680): getScriptSource and getWasmBytecode return Wasm wire bytes 74// as protocol::Binary, which is encoded as JSON string in the communication 75// to the DevTools front-end and hence leads to either crashing the renderer 76// that is being debugged or the renderer that's running the front-end if we 77// allow arbitrarily big Wasm byte sequences here. Ideally we would find a 78// different way to transfer the wire bytes (middle- to long-term), but as a 79// short-term solution, we should at least not crash. 80static constexpr size_t kWasmBytecodeMaxLength = 81 (v8::String::kMaxLength / 4) * 3; 82static constexpr const char kWasmBytecodeExceedsTransferLimit[] = 83 "WebAssembly bytecode exceeds the transfer limit"; 84#endif // V8_ENABLE_WEBASSEMBLY 85 86namespace { 87 88enum class BreakpointType { 89 kByUrl = 1, 90 kByUrlRegex, 91 kByScriptHash, 92 kByScriptId, 93 kDebugCommand, 94 kMonitorCommand, 95 kBreakpointAtEntry, 96 kInstrumentationBreakpoint 97}; 98 99String16 generateBreakpointId(BreakpointType type, 100 const String16& scriptSelector, int lineNumber, 101 int columnNumber) { 102 String16Builder builder; 103 builder.appendNumber(static_cast<int>(type)); 104 builder.append(':'); 105 builder.appendNumber(lineNumber); 106 builder.append(':'); 107 builder.appendNumber(columnNumber); 108 builder.append(':'); 109 builder.append(scriptSelector); 110 return builder.toString(); 111} 112 113String16 generateBreakpointId(BreakpointType type, 114 v8::Local<v8::Function> function) { 115 String16Builder builder; 116 builder.appendNumber(static_cast<int>(type)); 117 builder.append(':'); 118 builder.appendNumber(v8::debug::GetDebuggingId(function)); 119 return builder.toString(); 120} 121 122String16 generateInstrumentationBreakpointId(const String16& instrumentation) { 123 String16Builder builder; 124 builder.appendNumber( 125 static_cast<int>(BreakpointType::kInstrumentationBreakpoint)); 126 builder.append(':'); 127 builder.append(instrumentation); 128 return builder.toString(); 129} 130 131bool parseBreakpointId(const String16& breakpointId, BreakpointType* type, 132 String16* scriptSelector = nullptr, 133 int* lineNumber = nullptr, int* columnNumber = nullptr) { 134 size_t typeLineSeparator = breakpointId.find(':'); 135 if (typeLineSeparator == String16::kNotFound) return false; 136 137 int rawType = breakpointId.substring(0, typeLineSeparator).toInteger(); 138 if (rawType < static_cast<int>(BreakpointType::kByUrl) || 139 rawType > static_cast<int>(BreakpointType::kInstrumentationBreakpoint)) { 140 return false; 141 } 142 if (type) *type = static_cast<BreakpointType>(rawType); 143 if (rawType == static_cast<int>(BreakpointType::kDebugCommand) || 144 rawType == static_cast<int>(BreakpointType::kMonitorCommand) || 145 rawType == static_cast<int>(BreakpointType::kBreakpointAtEntry) || 146 rawType == static_cast<int>(BreakpointType::kInstrumentationBreakpoint)) { 147 // The script and source position are not encoded in this case. 148 return true; 149 } 150 151 size_t lineColumnSeparator = breakpointId.find(':', typeLineSeparator + 1); 152 if (lineColumnSeparator == String16::kNotFound) return false; 153 size_t columnSelectorSeparator = 154 breakpointId.find(':', lineColumnSeparator + 1); 155 if (columnSelectorSeparator == String16::kNotFound) return false; 156 if (scriptSelector) { 157 *scriptSelector = breakpointId.substring(columnSelectorSeparator + 1); 158 } 159 if (lineNumber) { 160 *lineNumber = breakpointId 161 .substring(typeLineSeparator + 1, 162 lineColumnSeparator - typeLineSeparator - 1) 163 .toInteger(); 164 } 165 if (columnNumber) { 166 *columnNumber = 167 breakpointId 168 .substring(lineColumnSeparator + 1, 169 columnSelectorSeparator - lineColumnSeparator - 1) 170 .toInteger(); 171 } 172 return true; 173} 174 175bool positionComparator(const std::pair<int, int>& a, 176 const std::pair<int, int>& b) { 177 if (a.first != b.first) return a.first < b.first; 178 return a.second < b.second; 179} 180 181String16 breakpointHint(const V8DebuggerScript& script, int lineNumber, 182 int columnNumber) { 183 int offset = script.offset(lineNumber, columnNumber); 184 if (offset == V8DebuggerScript::kNoOffset) return String16(); 185 String16 hint = 186 script.source(offset, kBreakpointHintMaxLength).stripWhiteSpace(); 187 for (size_t i = 0; i < hint.length(); ++i) { 188 if (hint[i] == '\r' || hint[i] == '\n' || hint[i] == ';') { 189 return hint.substring(0, i); 190 } 191 } 192 return hint; 193} 194 195void adjustBreakpointLocation(const V8DebuggerScript& script, 196 const String16& hint, int* lineNumber, 197 int* columnNumber) { 198 if (*lineNumber < script.startLine() || *lineNumber > script.endLine()) 199 return; 200 if (*lineNumber == script.startLine() && 201 *columnNumber < script.startColumn()) { 202 return; 203 } 204 if (*lineNumber == script.endLine() && script.endColumn() < *columnNumber) { 205 return; 206 } 207 208 if (hint.isEmpty()) return; 209 intptr_t sourceOffset = script.offset(*lineNumber, *columnNumber); 210 if (sourceOffset == V8DebuggerScript::kNoOffset) return; 211 212 intptr_t searchRegionOffset = std::max( 213 sourceOffset - kBreakpointHintMaxSearchOffset, static_cast<intptr_t>(0)); 214 size_t offset = sourceOffset - searchRegionOffset; 215 String16 searchArea = script.source(searchRegionOffset, 216 offset + kBreakpointHintMaxSearchOffset); 217 218 size_t nextMatch = searchArea.find(hint, offset); 219 size_t prevMatch = searchArea.reverseFind(hint, offset); 220 if (nextMatch == String16::kNotFound && prevMatch == String16::kNotFound) { 221 return; 222 } 223 size_t bestMatch; 224 if (nextMatch == String16::kNotFound) { 225 bestMatch = prevMatch; 226 } else if (prevMatch == String16::kNotFound) { 227 bestMatch = nextMatch; 228 } else { 229 bestMatch = nextMatch - offset < offset - prevMatch ? nextMatch : prevMatch; 230 } 231 bestMatch += searchRegionOffset; 232 v8::debug::Location hintPosition = 233 script.location(static_cast<int>(bestMatch)); 234 if (hintPosition.IsEmpty()) return; 235 *lineNumber = hintPosition.GetLineNumber(); 236 *columnNumber = hintPosition.GetColumnNumber(); 237} 238 239String16 breakLocationType(v8::debug::BreakLocationType type) { 240 switch (type) { 241 case v8::debug::kCallBreakLocation: 242 return protocol::Debugger::BreakLocation::TypeEnum::Call; 243 case v8::debug::kReturnBreakLocation: 244 return protocol::Debugger::BreakLocation::TypeEnum::Return; 245 case v8::debug::kDebuggerStatementBreakLocation: 246 return protocol::Debugger::BreakLocation::TypeEnum::DebuggerStatement; 247 case v8::debug::kCommonBreakLocation: 248 return String16(); 249 } 250 return String16(); 251} 252 253String16 scopeType(v8::debug::ScopeIterator::ScopeType type) { 254 switch (type) { 255 case v8::debug::ScopeIterator::ScopeTypeGlobal: 256 return Scope::TypeEnum::Global; 257 case v8::debug::ScopeIterator::ScopeTypeLocal: 258 return Scope::TypeEnum::Local; 259 case v8::debug::ScopeIterator::ScopeTypeWith: 260 return Scope::TypeEnum::With; 261 case v8::debug::ScopeIterator::ScopeTypeClosure: 262 return Scope::TypeEnum::Closure; 263 case v8::debug::ScopeIterator::ScopeTypeCatch: 264 return Scope::TypeEnum::Catch; 265 case v8::debug::ScopeIterator::ScopeTypeBlock: 266 return Scope::TypeEnum::Block; 267 case v8::debug::ScopeIterator::ScopeTypeScript: 268 return Scope::TypeEnum::Script; 269 case v8::debug::ScopeIterator::ScopeTypeEval: 270 return Scope::TypeEnum::Eval; 271 case v8::debug::ScopeIterator::ScopeTypeModule: 272 return Scope::TypeEnum::Module; 273 case v8::debug::ScopeIterator::ScopeTypeWasmExpressionStack: 274 return Scope::TypeEnum::WasmExpressionStack; 275 } 276 UNREACHABLE(); 277} 278 279Response buildScopes(v8::Isolate* isolate, v8::debug::ScopeIterator* iterator, 280 InjectedScript* injectedScript, 281 std::unique_ptr<Array<Scope>>* scopes) { 282 *scopes = std::make_unique<Array<Scope>>(); 283 if (!injectedScript) return Response::Success(); 284 if (iterator->Done()) return Response::Success(); 285 286 String16 scriptId = String16::fromInteger(iterator->GetScriptId()); 287 288 for (; !iterator->Done(); iterator->Advance()) { 289 std::unique_ptr<RemoteObject> object; 290 Response result = 291 injectedScript->wrapObject(iterator->GetObject(), kBacktraceObjectGroup, 292 WrapMode::kNoPreview, &object); 293 if (!result.IsSuccess()) return result; 294 295 auto scope = Scope::create() 296 .setType(scopeType(iterator->GetType())) 297 .setObject(std::move(object)) 298 .build(); 299 300 String16 name = toProtocolStringWithTypeCheck( 301 isolate, iterator->GetFunctionDebugName()); 302 if (!name.isEmpty()) scope->setName(name); 303 304 if (iterator->HasLocationInfo()) { 305 v8::debug::Location start = iterator->GetStartLocation(); 306 scope->setStartLocation(protocol::Debugger::Location::create() 307 .setScriptId(scriptId) 308 .setLineNumber(start.GetLineNumber()) 309 .setColumnNumber(start.GetColumnNumber()) 310 .build()); 311 312 v8::debug::Location end = iterator->GetEndLocation(); 313 scope->setEndLocation(protocol::Debugger::Location::create() 314 .setScriptId(scriptId) 315 .setLineNumber(end.GetLineNumber()) 316 .setColumnNumber(end.GetColumnNumber()) 317 .build()); 318 } 319 (*scopes)->emplace_back(std::move(scope)); 320 } 321 return Response::Success(); 322} 323 324protocol::DictionaryValue* getOrCreateObject(protocol::DictionaryValue* object, 325 const String16& key) { 326 protocol::DictionaryValue* value = object->getObject(key); 327 if (value) return value; 328 std::unique_ptr<protocol::DictionaryValue> newDictionary = 329 protocol::DictionaryValue::create(); 330 value = newDictionary.get(); 331 object->setObject(key, std::move(newDictionary)); 332 return value; 333} 334 335Response isValidPosition(protocol::Debugger::ScriptPosition* position) { 336 if (position->getLineNumber() < 0) 337 return Response::ServerError("Position missing 'line' or 'line' < 0."); 338 if (position->getColumnNumber() < 0) 339 return Response::ServerError("Position missing 'column' or 'column' < 0."); 340 return Response::Success(); 341} 342 343Response isValidRangeOfPositions(std::vector<std::pair<int, int>>& positions) { 344 for (size_t i = 1; i < positions.size(); ++i) { 345 if (positions[i - 1].first < positions[i].first) continue; 346 if (positions[i - 1].first == positions[i].first && 347 positions[i - 1].second < positions[i].second) 348 continue; 349 return Response::ServerError( 350 "Input positions array is not sorted or contains duplicate values."); 351 } 352 return Response::Success(); 353} 354 355bool hitBreakReasonEncodedAsOther(v8::debug::BreakReasons breakReasons) { 356 // The listed break reasons are not explicitly encoded in CDP when 357 // reporting the break. They are summarized as 'other'. 358 v8::debug::BreakReasons otherBreakReasons( 359 {v8::debug::BreakReason::kStep, 360 v8::debug::BreakReason::kDebuggerStatement, 361 v8::debug::BreakReason::kScheduled, v8::debug::BreakReason::kAsyncStep, 362 v8::debug::BreakReason::kAlreadyPaused}); 363 return breakReasons.contains_any(otherBreakReasons); 364} 365} // namespace 366 367V8DebuggerAgentImpl::V8DebuggerAgentImpl( 368 V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel, 369 protocol::DictionaryValue* state) 370 : m_inspector(session->inspector()), 371 m_debugger(m_inspector->debugger()), 372 m_session(session), 373 m_enabled(false), 374 m_state(state), 375 m_frontend(frontendChannel), 376 m_isolate(m_inspector->isolate()) {} 377 378V8DebuggerAgentImpl::~V8DebuggerAgentImpl() = default; 379 380void V8DebuggerAgentImpl::enableImpl() { 381 m_enabled = true; 382 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, true); 383 m_debugger->enable(); 384 385 std::vector<std::unique_ptr<V8DebuggerScript>> compiledScripts = 386 m_debugger->getCompiledScripts(m_session->contextGroupId(), this); 387 for (auto& script : compiledScripts) { 388 didParseSource(std::move(script), true); 389 } 390 391 m_breakpointsActive = true; 392 m_debugger->setBreakpointsActive(true); 393 394 if (isPaused()) { 395 didPause(0, v8::Local<v8::Value>(), std::vector<v8::debug::BreakpointId>(), 396 v8::debug::kException, false, 397 v8::debug::BreakReasons({v8::debug::BreakReason::kAlreadyPaused})); 398 } 399} 400 401Response V8DebuggerAgentImpl::enable(Maybe<double> maxScriptsCacheSize, 402 String16* outDebuggerId) { 403 m_maxScriptCacheSize = v8::base::saturated_cast<size_t>( 404 maxScriptsCacheSize.fromMaybe(std::numeric_limits<double>::max())); 405 *outDebuggerId = 406 m_debugger->debuggerIdFor(m_session->contextGroupId()).toString(); 407 if (enabled()) return Response::Success(); 408 409 if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId())) 410 return Response::ServerError("Script execution is prohibited"); 411 412 enableImpl(); 413 return Response::Success(); 414} 415 416Response V8DebuggerAgentImpl::disable() { 417 if (!enabled()) return Response::Success(); 418 419 m_state->remove(DebuggerAgentState::breakpointsByRegex); 420 m_state->remove(DebuggerAgentState::breakpointsByUrl); 421 m_state->remove(DebuggerAgentState::breakpointsByScriptHash); 422 m_state->remove(DebuggerAgentState::breakpointHints); 423 m_state->remove(DebuggerAgentState::instrumentationBreakpoints); 424 425 m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState, 426 v8::debug::NoBreakOnException); 427 m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, 0); 428 429 if (m_breakpointsActive) { 430 m_debugger->setBreakpointsActive(false); 431 m_breakpointsActive = false; 432 } 433 m_blackboxedPositions.clear(); 434 m_blackboxPattern.reset(); 435 resetBlackboxedStateCache(); 436 m_skipList.clear(); 437 m_scripts.clear(); 438 m_cachedScripts.clear(); 439 m_cachedScriptSize = 0; 440 for (const auto& it : m_debuggerBreakpointIdToBreakpointId) { 441 v8::debug::RemoveBreakpoint(m_isolate, it.first); 442 } 443 m_breakpointIdToDebuggerBreakpointIds.clear(); 444 m_debuggerBreakpointIdToBreakpointId.clear(); 445 m_debugger->setAsyncCallStackDepth(this, 0); 446 clearBreakDetails(); 447 m_skipAllPauses = false; 448 m_state->setBoolean(DebuggerAgentState::skipAllPauses, false); 449 m_state->remove(DebuggerAgentState::blackboxPattern); 450 m_enabled = false; 451 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false); 452 m_debugger->disable(); 453 return Response::Success(); 454} 455 456void V8DebuggerAgentImpl::restore() { 457 DCHECK(!m_enabled); 458 if (!m_state->booleanProperty(DebuggerAgentState::debuggerEnabled, false)) 459 return; 460 if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId())) 461 return; 462 463 enableImpl(); 464 465 int pauseState = v8::debug::NoBreakOnException; 466 m_state->getInteger(DebuggerAgentState::pauseOnExceptionsState, &pauseState); 467 setPauseOnExceptionsImpl(pauseState); 468 469 m_skipAllPauses = 470 m_state->booleanProperty(DebuggerAgentState::skipAllPauses, false); 471 472 int asyncCallStackDepth = 0; 473 m_state->getInteger(DebuggerAgentState::asyncCallStackDepth, 474 &asyncCallStackDepth); 475 m_debugger->setAsyncCallStackDepth(this, asyncCallStackDepth); 476 477 String16 blackboxPattern; 478 if (m_state->getString(DebuggerAgentState::blackboxPattern, 479 &blackboxPattern)) { 480 setBlackboxPattern(blackboxPattern); 481 } 482} 483 484Response V8DebuggerAgentImpl::setBreakpointsActive(bool active) { 485 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 486 if (m_breakpointsActive == active) return Response::Success(); 487 m_breakpointsActive = active; 488 m_debugger->setBreakpointsActive(active); 489 if (!active && !m_breakReason.empty()) { 490 clearBreakDetails(); 491 m_debugger->setPauseOnNextCall(false, m_session->contextGroupId()); 492 } 493 return Response::Success(); 494} 495 496Response V8DebuggerAgentImpl::setSkipAllPauses(bool skip) { 497 m_state->setBoolean(DebuggerAgentState::skipAllPauses, skip); 498 m_skipAllPauses = skip; 499 return Response::Success(); 500} 501 502static bool matches(V8InspectorImpl* inspector, const V8DebuggerScript& script, 503 BreakpointType type, const String16& selector) { 504 switch (type) { 505 case BreakpointType::kByUrl: 506 return script.sourceURL() == selector; 507 case BreakpointType::kByScriptHash: 508 return script.hash() == selector; 509 case BreakpointType::kByUrlRegex: { 510 V8Regex regex(inspector, selector, true); 511 return regex.match(script.sourceURL()) != -1; 512 } 513 case BreakpointType::kByScriptId: { 514 return script.scriptId() == selector; 515 } 516 default: 517 return false; 518 } 519} 520 521Response V8DebuggerAgentImpl::setBreakpointByUrl( 522 int lineNumber, Maybe<String16> optionalURL, 523 Maybe<String16> optionalURLRegex, Maybe<String16> optionalScriptHash, 524 Maybe<int> optionalColumnNumber, Maybe<String16> optionalCondition, 525 String16* outBreakpointId, 526 std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) { 527 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 528 529 *locations = std::make_unique<Array<protocol::Debugger::Location>>(); 530 531 int specified = (optionalURL.isJust() ? 1 : 0) + 532 (optionalURLRegex.isJust() ? 1 : 0) + 533 (optionalScriptHash.isJust() ? 1 : 0); 534 if (specified != 1) { 535 return Response::ServerError( 536 "Either url or urlRegex or scriptHash must be specified."); 537 } 538 int columnNumber = 0; 539 if (optionalColumnNumber.isJust()) { 540 columnNumber = optionalColumnNumber.fromJust(); 541 if (columnNumber < 0) 542 return Response::ServerError("Incorrect column number"); 543 } 544 545 BreakpointType type = BreakpointType::kByUrl; 546 String16 selector; 547 if (optionalURLRegex.isJust()) { 548 selector = optionalURLRegex.fromJust(); 549 type = BreakpointType::kByUrlRegex; 550 } else if (optionalURL.isJust()) { 551 selector = optionalURL.fromJust(); 552 type = BreakpointType::kByUrl; 553 } else if (optionalScriptHash.isJust()) { 554 selector = optionalScriptHash.fromJust(); 555 type = BreakpointType::kByScriptHash; 556 } 557 558 String16 condition = optionalCondition.fromMaybe(String16()); 559 String16 breakpointId = 560 generateBreakpointId(type, selector, lineNumber, columnNumber); 561 protocol::DictionaryValue* breakpoints; 562 switch (type) { 563 case BreakpointType::kByUrlRegex: 564 breakpoints = 565 getOrCreateObject(m_state, DebuggerAgentState::breakpointsByRegex); 566 break; 567 case BreakpointType::kByUrl: 568 breakpoints = getOrCreateObject( 569 getOrCreateObject(m_state, DebuggerAgentState::breakpointsByUrl), 570 selector); 571 break; 572 case BreakpointType::kByScriptHash: 573 breakpoints = getOrCreateObject( 574 getOrCreateObject(m_state, 575 DebuggerAgentState::breakpointsByScriptHash), 576 selector); 577 break; 578 default: 579 UNREACHABLE(); 580 } 581 if (breakpoints->get(breakpointId)) { 582 return Response::ServerError( 583 "Breakpoint at specified location already exists."); 584 } 585 586 String16 hint; 587 for (const auto& script : m_scripts) { 588 if (!matches(m_inspector, *script.second, type, selector)) continue; 589 if (!hint.isEmpty()) { 590 adjustBreakpointLocation(*script.second, hint, &lineNumber, 591 &columnNumber); 592 } 593 std::unique_ptr<protocol::Debugger::Location> location = setBreakpointImpl( 594 breakpointId, script.first, condition, lineNumber, columnNumber); 595 if (location && type != BreakpointType::kByUrlRegex) { 596 hint = breakpointHint(*script.second, location->getLineNumber(), 597 location->getColumnNumber(columnNumber)); 598 } 599 if (location) (*locations)->emplace_back(std::move(location)); 600 } 601 breakpoints->setString(breakpointId, condition); 602 if (!hint.isEmpty()) { 603 protocol::DictionaryValue* breakpointHints = 604 getOrCreateObject(m_state, DebuggerAgentState::breakpointHints); 605 breakpointHints->setString(breakpointId, hint); 606 } 607 *outBreakpointId = breakpointId; 608 return Response::Success(); 609} 610 611Response V8DebuggerAgentImpl::setBreakpoint( 612 std::unique_ptr<protocol::Debugger::Location> location, 613 Maybe<String16> optionalCondition, String16* outBreakpointId, 614 std::unique_ptr<protocol::Debugger::Location>* actualLocation) { 615 String16 breakpointId = generateBreakpointId( 616 BreakpointType::kByScriptId, location->getScriptId(), 617 location->getLineNumber(), location->getColumnNumber(0)); 618 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 619 620 if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) != 621 m_breakpointIdToDebuggerBreakpointIds.end()) { 622 return Response::ServerError( 623 "Breakpoint at specified location already exists."); 624 } 625 *actualLocation = setBreakpointImpl(breakpointId, location->getScriptId(), 626 optionalCondition.fromMaybe(String16()), 627 location->getLineNumber(), 628 location->getColumnNumber(0)); 629 if (!*actualLocation) 630 return Response::ServerError("Could not resolve breakpoint"); 631 *outBreakpointId = breakpointId; 632 return Response::Success(); 633} 634 635Response V8DebuggerAgentImpl::setBreakpointOnFunctionCall( 636 const String16& functionObjectId, Maybe<String16> optionalCondition, 637 String16* outBreakpointId) { 638 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 639 640 InjectedScript::ObjectScope scope(m_session, functionObjectId); 641 Response response = scope.initialize(); 642 if (!response.IsSuccess()) return response; 643 if (!scope.object()->IsFunction()) { 644 return Response::ServerError("Could not find function with given id"); 645 } 646 v8::Local<v8::Function> function = 647 v8::Local<v8::Function>::Cast(scope.object()); 648 String16 breakpointId = 649 generateBreakpointId(BreakpointType::kBreakpointAtEntry, function); 650 if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) != 651 m_breakpointIdToDebuggerBreakpointIds.end()) { 652 return Response::ServerError( 653 "Breakpoint at specified location already exists."); 654 } 655 v8::Local<v8::String> condition = 656 toV8String(m_isolate, optionalCondition.fromMaybe(String16())); 657 setBreakpointImpl(breakpointId, function, condition); 658 *outBreakpointId = breakpointId; 659 return Response::Success(); 660} 661 662Response V8DebuggerAgentImpl::setInstrumentationBreakpoint( 663 const String16& instrumentation, String16* outBreakpointId) { 664 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 665 String16 breakpointId = generateInstrumentationBreakpointId(instrumentation); 666 protocol::DictionaryValue* breakpoints = getOrCreateObject( 667 m_state, DebuggerAgentState::instrumentationBreakpoints); 668 if (breakpoints->get(breakpointId)) { 669 return Response::ServerError( 670 "Instrumentation breakpoint is already enabled."); 671 } 672 breakpoints->setBoolean(breakpointId, true); 673 *outBreakpointId = breakpointId; 674 return Response::Success(); 675} 676 677Response V8DebuggerAgentImpl::removeBreakpoint(const String16& breakpointId) { 678 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 679 BreakpointType type; 680 String16 selector; 681 if (!parseBreakpointId(breakpointId, &type, &selector)) { 682 return Response::Success(); 683 } 684 protocol::DictionaryValue* breakpoints = nullptr; 685 switch (type) { 686 case BreakpointType::kByUrl: { 687 protocol::DictionaryValue* breakpointsByUrl = 688 m_state->getObject(DebuggerAgentState::breakpointsByUrl); 689 if (breakpointsByUrl) { 690 breakpoints = breakpointsByUrl->getObject(selector); 691 } 692 } break; 693 case BreakpointType::kByScriptHash: { 694 protocol::DictionaryValue* breakpointsByScriptHash = 695 m_state->getObject(DebuggerAgentState::breakpointsByScriptHash); 696 if (breakpointsByScriptHash) { 697 breakpoints = breakpointsByScriptHash->getObject(selector); 698 } 699 } break; 700 case BreakpointType::kByUrlRegex: 701 breakpoints = m_state->getObject(DebuggerAgentState::breakpointsByRegex); 702 break; 703 case BreakpointType::kInstrumentationBreakpoint: 704 breakpoints = 705 m_state->getObject(DebuggerAgentState::instrumentationBreakpoints); 706 break; 707 default: 708 break; 709 } 710 if (breakpoints) breakpoints->remove(breakpointId); 711 protocol::DictionaryValue* breakpointHints = 712 m_state->getObject(DebuggerAgentState::breakpointHints); 713 if (breakpointHints) breakpointHints->remove(breakpointId); 714 715 // Get a list of scripts to remove breakpoints. 716 // TODO(duongn): we can do better here if from breakpoint id we can tell it is 717 // not Wasm breakpoint. 718 std::vector<V8DebuggerScript*> scripts; 719 for (const auto& scriptIter : m_scripts) { 720 const bool scriptSelectorMatch = 721 matches(m_inspector, *scriptIter.second, type, selector); 722 const bool isInstrumentation = 723 type == BreakpointType::kInstrumentationBreakpoint; 724 if (!scriptSelectorMatch && !isInstrumentation) continue; 725 V8DebuggerScript* script = scriptIter.second.get(); 726 if (script->getLanguage() == V8DebuggerScript::Language::WebAssembly) { 727 scripts.push_back(script); 728 } 729 } 730 removeBreakpointImpl(breakpointId, scripts); 731 732 return Response::Success(); 733} 734 735void V8DebuggerAgentImpl::removeBreakpointImpl( 736 const String16& breakpointId, 737 const std::vector<V8DebuggerScript*>& scripts) { 738 DCHECK(enabled()); 739 BreakpointIdToDebuggerBreakpointIdsMap::iterator 740 debuggerBreakpointIdsIterator = 741 m_breakpointIdToDebuggerBreakpointIds.find(breakpointId); 742 if (debuggerBreakpointIdsIterator == 743 m_breakpointIdToDebuggerBreakpointIds.end()) { 744 return; 745 } 746 for (const auto& id : debuggerBreakpointIdsIterator->second) { 747#if V8_ENABLE_WEBASSEMBLY 748 for (auto& script : scripts) { 749 script->removeWasmBreakpoint(id); 750 } 751#endif // V8_ENABLE_WEBASSEMBLY 752 v8::debug::RemoveBreakpoint(m_isolate, id); 753 m_debuggerBreakpointIdToBreakpointId.erase(id); 754 } 755 m_breakpointIdToDebuggerBreakpointIds.erase(breakpointId); 756} 757 758Response V8DebuggerAgentImpl::getPossibleBreakpoints( 759 std::unique_ptr<protocol::Debugger::Location> start, 760 Maybe<protocol::Debugger::Location> end, Maybe<bool> restrictToFunction, 761 std::unique_ptr<protocol::Array<protocol::Debugger::BreakLocation>>* 762 locations) { 763 String16 scriptId = start->getScriptId(); 764 765 if (start->getLineNumber() < 0 || start->getColumnNumber(0) < 0) 766 return Response::ServerError( 767 "start.lineNumber and start.columnNumber should be >= 0"); 768 769 v8::debug::Location v8Start(start->getLineNumber(), 770 start->getColumnNumber(0)); 771 v8::debug::Location v8End; 772 if (end.isJust()) { 773 if (end.fromJust()->getScriptId() != scriptId) 774 return Response::ServerError( 775 "Locations should contain the same scriptId"); 776 int line = end.fromJust()->getLineNumber(); 777 int column = end.fromJust()->getColumnNumber(0); 778 if (line < 0 || column < 0) 779 return Response::ServerError( 780 "end.lineNumber and end.columnNumber should be >= 0"); 781 v8End = v8::debug::Location(line, column); 782 } 783 auto it = m_scripts.find(scriptId); 784 if (it == m_scripts.end()) return Response::ServerError("Script not found"); 785 std::vector<v8::debug::BreakLocation> v8Locations; 786 { 787 v8::HandleScope handleScope(m_isolate); 788 int contextId = it->second->executionContextId(); 789 InspectedContext* inspected = m_inspector->getContext(contextId); 790 if (!inspected) { 791 return Response::ServerError("Cannot retrive script context"); 792 } 793 v8::Context::Scope contextScope(inspected->context()); 794 v8::MicrotasksScope microtasks(m_isolate, 795 v8::MicrotasksScope::kDoNotRunMicrotasks); 796 v8::TryCatch tryCatch(m_isolate); 797 it->second->getPossibleBreakpoints( 798 v8Start, v8End, restrictToFunction.fromMaybe(false), &v8Locations); 799 } 800 801 *locations = 802 std::make_unique<protocol::Array<protocol::Debugger::BreakLocation>>(); 803 804 // TODO(1106269): Return an error instead of capping the number of 805 // breakpoints. 806 const size_t numBreakpointsToSend = 807 std::min(v8Locations.size(), kMaxNumBreakpoints); 808 for (size_t i = 0; i < numBreakpointsToSend; ++i) { 809 std::unique_ptr<protocol::Debugger::BreakLocation> breakLocation = 810 protocol::Debugger::BreakLocation::create() 811 .setScriptId(scriptId) 812 .setLineNumber(v8Locations[i].GetLineNumber()) 813 .setColumnNumber(v8Locations[i].GetColumnNumber()) 814 .build(); 815 if (v8Locations[i].type() != v8::debug::kCommonBreakLocation) { 816 breakLocation->setType(breakLocationType(v8Locations[i].type())); 817 } 818 (*locations)->emplace_back(std::move(breakLocation)); 819 } 820 return Response::Success(); 821} 822 823Response V8DebuggerAgentImpl::continueToLocation( 824 std::unique_ptr<protocol::Debugger::Location> location, 825 Maybe<String16> targetCallFrames) { 826 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 827 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); 828 ScriptsMap::iterator it = m_scripts.find(location->getScriptId()); 829 if (it == m_scripts.end()) { 830 return Response::ServerError("Cannot continue to specified location"); 831 } 832 V8DebuggerScript* script = it->second.get(); 833 int contextId = script->executionContextId(); 834 InspectedContext* inspected = m_inspector->getContext(contextId); 835 if (!inspected) 836 return Response::ServerError("Cannot continue to specified location"); 837 v8::HandleScope handleScope(m_isolate); 838 v8::Context::Scope contextScope(inspected->context()); 839 return m_debugger->continueToLocation( 840 m_session->contextGroupId(), script, std::move(location), 841 targetCallFrames.fromMaybe( 842 protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any)); 843} 844 845Response V8DebuggerAgentImpl::getStackTrace( 846 std::unique_ptr<protocol::Runtime::StackTraceId> inStackTraceId, 847 std::unique_ptr<protocol::Runtime::StackTrace>* outStackTrace) { 848 bool isOk = false; 849 int64_t id = inStackTraceId->getId().toInteger64(&isOk); 850 if (!isOk) return Response::ServerError("Invalid stack trace id"); 851 852 internal::V8DebuggerId debuggerId; 853 if (inStackTraceId->hasDebuggerId()) { 854 debuggerId = 855 internal::V8DebuggerId(inStackTraceId->getDebuggerId(String16())); 856 } else { 857 debuggerId = m_debugger->debuggerIdFor(m_session->contextGroupId()); 858 } 859 if (!debuggerId.isValid()) 860 return Response::ServerError("Invalid stack trace id"); 861 862 V8StackTraceId v8StackTraceId(id, debuggerId.pair()); 863 if (v8StackTraceId.IsInvalid()) 864 return Response::ServerError("Invalid stack trace id"); 865 auto stack = 866 m_debugger->stackTraceFor(m_session->contextGroupId(), v8StackTraceId); 867 if (!stack) { 868 return Response::ServerError("Stack trace with given id is not found"); 869 } 870 *outStackTrace = stack->buildInspectorObject( 871 m_debugger, m_debugger->maxAsyncCallChainDepth()); 872 return Response::Success(); 873} 874 875bool V8DebuggerAgentImpl::isFunctionBlackboxed(const String16& scriptId, 876 const v8::debug::Location& start, 877 const v8::debug::Location& end) { 878 ScriptsMap::iterator it = m_scripts.find(scriptId); 879 if (it == m_scripts.end()) { 880 // Unknown scripts are blackboxed. 881 return true; 882 } 883 if (m_blackboxPattern) { 884 const String16& scriptSourceURL = it->second->sourceURL(); 885 if (!scriptSourceURL.isEmpty() && 886 m_blackboxPattern->match(scriptSourceURL) != -1) 887 return true; 888 } 889 auto itBlackboxedPositions = m_blackboxedPositions.find(scriptId); 890 if (itBlackboxedPositions == m_blackboxedPositions.end()) return false; 891 892 const std::vector<std::pair<int, int>>& ranges = 893 itBlackboxedPositions->second; 894 auto itStartRange = std::lower_bound( 895 ranges.begin(), ranges.end(), 896 std::make_pair(start.GetLineNumber(), start.GetColumnNumber()), 897 positionComparator); 898 auto itEndRange = std::lower_bound( 899 itStartRange, ranges.end(), 900 std::make_pair(end.GetLineNumber(), end.GetColumnNumber()), 901 positionComparator); 902 // Ranges array contains positions in script where blackbox state is changed. 903 // [(0,0) ... ranges[0]) isn't blackboxed, [ranges[0] ... ranges[1]) is 904 // blackboxed... 905 return itStartRange == itEndRange && 906 std::distance(ranges.begin(), itStartRange) % 2; 907} 908 909bool V8DebuggerAgentImpl::shouldBeSkipped(const String16& scriptId, int line, 910 int column) { 911 if (m_skipList.empty()) return false; 912 913 auto it = m_skipList.find(scriptId); 914 if (it == m_skipList.end()) return false; 915 916 const std::vector<std::pair<int, int>>& ranges = it->second; 917 DCHECK(!ranges.empty()); 918 const std::pair<int, int> location = std::make_pair(line, column); 919 auto itLowerBound = std::lower_bound(ranges.begin(), ranges.end(), location, 920 positionComparator); 921 922 bool shouldSkip = false; 923 if (itLowerBound != ranges.end()) { 924 // Skip lists are defined as pairs of locations that specify the 925 // start and the end of ranges to skip: [ranges[0], ranges[1], ..], where 926 // locations in [ranges[0], ranges[1]) should be skipped, i.e. 927 // [(lineStart, columnStart), (lineEnd, columnEnd)). 928 const bool isSameAsLowerBound = location == *itLowerBound; 929 const bool isUnevenIndex = (itLowerBound - ranges.begin()) % 2; 930 shouldSkip = isSameAsLowerBound ^ isUnevenIndex; 931 } 932 933 return shouldSkip; 934} 935 936bool V8DebuggerAgentImpl::acceptsPause(bool isOOMBreak) const { 937 return enabled() && (isOOMBreak || !m_skipAllPauses); 938} 939 940std::unique_ptr<protocol::Debugger::Location> 941V8DebuggerAgentImpl::setBreakpointImpl(const String16& breakpointId, 942 const String16& scriptId, 943 const String16& condition, 944 int lineNumber, int columnNumber) { 945 v8::HandleScope handles(m_isolate); 946 DCHECK(enabled()); 947 948 ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId); 949 if (scriptIterator == m_scripts.end()) return nullptr; 950 V8DebuggerScript* script = scriptIterator->second.get(); 951 if (lineNumber < script->startLine() || script->endLine() < lineNumber) { 952 return nullptr; 953 } 954 if (lineNumber == script->startLine() && 955 columnNumber < script->startColumn()) { 956 return nullptr; 957 } 958 if (lineNumber == script->endLine() && script->endColumn() < columnNumber) { 959 return nullptr; 960 } 961 962 v8::debug::BreakpointId debuggerBreakpointId; 963 v8::debug::Location location(lineNumber, columnNumber); 964 int contextId = script->executionContextId(); 965 InspectedContext* inspected = m_inspector->getContext(contextId); 966 if (!inspected) return nullptr; 967 968 { 969 v8::Context::Scope contextScope(inspected->context()); 970 if (!script->setBreakpoint(condition, &location, &debuggerBreakpointId)) { 971 return nullptr; 972 } 973 } 974 975 m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId; 976 m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back( 977 debuggerBreakpointId); 978 979 return protocol::Debugger::Location::create() 980 .setScriptId(scriptId) 981 .setLineNumber(location.GetLineNumber()) 982 .setColumnNumber(location.GetColumnNumber()) 983 .build(); 984} 985 986void V8DebuggerAgentImpl::setBreakpointImpl(const String16& breakpointId, 987 v8::Local<v8::Function> function, 988 v8::Local<v8::String> condition) { 989 v8::debug::BreakpointId debuggerBreakpointId; 990 if (!v8::debug::SetFunctionBreakpoint(function, condition, 991 &debuggerBreakpointId)) { 992 return; 993 } 994 m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId; 995 m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back( 996 debuggerBreakpointId); 997} 998 999Response V8DebuggerAgentImpl::searchInContent( 1000 const String16& scriptId, const String16& query, 1001 Maybe<bool> optionalCaseSensitive, Maybe<bool> optionalIsRegex, 1002 std::unique_ptr<Array<protocol::Debugger::SearchMatch>>* results) { 1003 v8::HandleScope handles(m_isolate); 1004 ScriptsMap::iterator it = m_scripts.find(scriptId); 1005 if (it == m_scripts.end()) 1006 return Response::ServerError("No script for id: " + scriptId.utf8()); 1007 1008 *results = std::make_unique<protocol::Array<protocol::Debugger::SearchMatch>>( 1009 searchInTextByLinesImpl(m_session, it->second->source(0), query, 1010 optionalCaseSensitive.fromMaybe(false), 1011 optionalIsRegex.fromMaybe(false))); 1012 return Response::Success(); 1013} 1014 1015Response V8DebuggerAgentImpl::setScriptSource( 1016 const String16& scriptId, const String16& newContent, Maybe<bool> dryRun, 1017 Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames, 1018 Maybe<bool>* stackChanged, 1019 Maybe<protocol::Runtime::StackTrace>* asyncStackTrace, 1020 Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId, 1021 Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) { 1022 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 1023 1024 ScriptsMap::iterator it = m_scripts.find(scriptId); 1025 if (it == m_scripts.end()) { 1026 return Response::ServerError("No script with given id found"); 1027 } 1028 int contextId = it->second->executionContextId(); 1029 InspectedContext* inspected = m_inspector->getContext(contextId); 1030 if (!inspected) { 1031 return Response::InternalError(); 1032 } 1033 v8::HandleScope handleScope(m_isolate); 1034 v8::Local<v8::Context> context = inspected->context(); 1035 v8::Context::Scope contextScope(context); 1036 1037 v8::debug::LiveEditResult result; 1038 it->second->setSource(newContent, dryRun.fromMaybe(false), &result); 1039 if (result.status != v8::debug::LiveEditResult::OK) { 1040 *optOutCompileError = 1041 protocol::Runtime::ExceptionDetails::create() 1042 .setExceptionId(m_inspector->nextExceptionId()) 1043 .setText(toProtocolString(m_isolate, result.message)) 1044 .setLineNumber(result.line_number != -1 ? result.line_number - 1 1045 : 0) 1046 .setColumnNumber(result.column_number != -1 ? result.column_number 1047 : 0) 1048 .build(); 1049 return Response::Success(); 1050 } else { 1051 *stackChanged = result.stack_changed; 1052 } 1053 std::unique_ptr<Array<CallFrame>> callFrames; 1054 Response response = currentCallFrames(&callFrames); 1055 if (!response.IsSuccess()) return response; 1056 *newCallFrames = std::move(callFrames); 1057 *asyncStackTrace = currentAsyncStackTrace(); 1058 *asyncStackTraceId = currentExternalStackTrace(); 1059 return Response::Success(); 1060} 1061 1062Response V8DebuggerAgentImpl::restartFrame( 1063 const String16& callFrameId, 1064 std::unique_ptr<Array<CallFrame>>* newCallFrames, 1065 Maybe<protocol::Runtime::StackTrace>* asyncStackTrace, 1066 Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId) { 1067 return Response::ServerError("Frame restarting not supported"); 1068} 1069 1070Response V8DebuggerAgentImpl::getScriptSource( 1071 const String16& scriptId, String16* scriptSource, 1072 Maybe<protocol::Binary>* bytecode) { 1073 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 1074 ScriptsMap::iterator it = m_scripts.find(scriptId); 1075 if (it == m_scripts.end()) { 1076 auto cachedScriptIt = 1077 std::find_if(m_cachedScripts.begin(), m_cachedScripts.end(), 1078 [&scriptId](const CachedScript& cachedScript) { 1079 return cachedScript.scriptId == scriptId; 1080 }); 1081 if (cachedScriptIt != m_cachedScripts.end()) { 1082 *scriptSource = cachedScriptIt->source; 1083 *bytecode = protocol::Binary::fromSpan(cachedScriptIt->bytecode.data(), 1084 cachedScriptIt->bytecode.size()); 1085 return Response::Success(); 1086 } 1087 return Response::ServerError("No script for id: " + scriptId.utf8()); 1088 } 1089 *scriptSource = it->second->source(0); 1090#if V8_ENABLE_WEBASSEMBLY 1091 v8::MemorySpan<const uint8_t> span; 1092 if (it->second->wasmBytecode().To(&span)) { 1093 if (span.size() > kWasmBytecodeMaxLength) { 1094 return Response::ServerError(kWasmBytecodeExceedsTransferLimit); 1095 } 1096 *bytecode = protocol::Binary::fromSpan(span.data(), span.size()); 1097 } 1098#endif // V8_ENABLE_WEBASSEMBLY 1099 return Response::Success(); 1100} 1101 1102Response V8DebuggerAgentImpl::getWasmBytecode(const String16& scriptId, 1103 protocol::Binary* bytecode) { 1104#if V8_ENABLE_WEBASSEMBLY 1105 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 1106 ScriptsMap::iterator it = m_scripts.find(scriptId); 1107 if (it == m_scripts.end()) 1108 return Response::ServerError("No script for id: " + scriptId.utf8()); 1109 v8::MemorySpan<const uint8_t> span; 1110 if (!it->second->wasmBytecode().To(&span)) 1111 return Response::ServerError("Script with id " + scriptId.utf8() + 1112 " is not WebAssembly"); 1113 if (span.size() > kWasmBytecodeMaxLength) { 1114 return Response::ServerError(kWasmBytecodeExceedsTransferLimit); 1115 } 1116 *bytecode = protocol::Binary::fromSpan(span.data(), span.size()); 1117 return Response::Success(); 1118#else 1119 return Response::ServerError("WebAssembly is disabled"); 1120#endif // V8_ENABLE_WEBASSEMBLY 1121} 1122 1123void V8DebuggerAgentImpl::pushBreakDetails( 1124 const String16& breakReason, 1125 std::unique_ptr<protocol::DictionaryValue> breakAuxData) { 1126 m_breakReason.push_back(std::make_pair(breakReason, std::move(breakAuxData))); 1127} 1128 1129void V8DebuggerAgentImpl::popBreakDetails() { 1130 if (m_breakReason.empty()) return; 1131 m_breakReason.pop_back(); 1132} 1133 1134void V8DebuggerAgentImpl::clearBreakDetails() { 1135 std::vector<BreakReason> emptyBreakReason; 1136 m_breakReason.swap(emptyBreakReason); 1137} 1138 1139void V8DebuggerAgentImpl::schedulePauseOnNextStatement( 1140 const String16& breakReason, 1141 std::unique_ptr<protocol::DictionaryValue> data) { 1142 if (isPaused() || !acceptsPause(false) || !m_breakpointsActive) return; 1143 if (m_breakReason.empty()) { 1144 m_debugger->setPauseOnNextCall(true, m_session->contextGroupId()); 1145 } 1146 pushBreakDetails(breakReason, std::move(data)); 1147} 1148 1149void V8DebuggerAgentImpl::cancelPauseOnNextStatement() { 1150 if (isPaused() || !acceptsPause(false) || !m_breakpointsActive) return; 1151 if (m_breakReason.size() == 1) { 1152 m_debugger->setPauseOnNextCall(false, m_session->contextGroupId()); 1153 } 1154 popBreakDetails(); 1155} 1156 1157Response V8DebuggerAgentImpl::pause() { 1158 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 1159 if (isPaused()) return Response::Success(); 1160 1161 if (m_debugger->canBreakProgram()) { 1162 m_debugger->interruptAndBreak(m_session->contextGroupId()); 1163 } else { 1164 pushBreakDetails(protocol::Debugger::Paused::ReasonEnum::Other, nullptr); 1165 m_debugger->setPauseOnNextCall(true, m_session->contextGroupId()); 1166 } 1167 1168 return Response::Success(); 1169} 1170 1171Response V8DebuggerAgentImpl::resume(Maybe<bool> terminateOnResume) { 1172 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); 1173 m_session->releaseObjectGroup(kBacktraceObjectGroup); 1174 m_debugger->continueProgram(m_session->contextGroupId(), 1175 terminateOnResume.fromMaybe(false)); 1176 return Response::Success(); 1177} 1178 1179Response V8DebuggerAgentImpl::stepOver( 1180 Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) { 1181 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); 1182 1183 if (inSkipList.isJust()) { 1184 const Response res = processSkipList(inSkipList.fromJust()); 1185 if (res.IsError()) return res; 1186 } else { 1187 m_skipList.clear(); 1188 } 1189 1190 m_session->releaseObjectGroup(kBacktraceObjectGroup); 1191 m_debugger->stepOverStatement(m_session->contextGroupId()); 1192 return Response::Success(); 1193} 1194 1195Response V8DebuggerAgentImpl::stepInto( 1196 Maybe<bool> inBreakOnAsyncCall, 1197 Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) { 1198 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); 1199 1200 if (inSkipList.isJust()) { 1201 const Response res = processSkipList(inSkipList.fromJust()); 1202 if (res.IsError()) return res; 1203 } else { 1204 m_skipList.clear(); 1205 } 1206 1207 m_session->releaseObjectGroup(kBacktraceObjectGroup); 1208 m_debugger->stepIntoStatement(m_session->contextGroupId(), 1209 inBreakOnAsyncCall.fromMaybe(false)); 1210 return Response::Success(); 1211} 1212 1213Response V8DebuggerAgentImpl::stepOut() { 1214 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); 1215 m_session->releaseObjectGroup(kBacktraceObjectGroup); 1216 m_debugger->stepOutOfFunction(m_session->contextGroupId()); 1217 return Response::Success(); 1218} 1219 1220Response V8DebuggerAgentImpl::pauseOnAsyncCall( 1221 std::unique_ptr<protocol::Runtime::StackTraceId> inParentStackTraceId) { 1222 // Deprecated, just return OK. 1223 return Response::Success(); 1224} 1225 1226Response V8DebuggerAgentImpl::setPauseOnExceptions( 1227 const String16& stringPauseState) { 1228 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 1229 v8::debug::ExceptionBreakState pauseState; 1230 if (stringPauseState == "none") { 1231 pauseState = v8::debug::NoBreakOnException; 1232 } else if (stringPauseState == "all") { 1233 pauseState = v8::debug::BreakOnAnyException; 1234 } else if (stringPauseState == "uncaught") { 1235 pauseState = v8::debug::BreakOnUncaughtException; 1236 } else { 1237 return Response::ServerError("Unknown pause on exceptions mode: " + 1238 stringPauseState.utf8()); 1239 } 1240 setPauseOnExceptionsImpl(pauseState); 1241 return Response::Success(); 1242} 1243 1244void V8DebuggerAgentImpl::setPauseOnExceptionsImpl(int pauseState) { 1245 // TODO(dgozman): this changes the global state and forces all context groups 1246 // to pause. We should make this flag be per-context-group. 1247 m_debugger->setPauseOnExceptionsState( 1248 static_cast<v8::debug::ExceptionBreakState>(pauseState)); 1249 m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState, pauseState); 1250} 1251 1252Response V8DebuggerAgentImpl::evaluateOnCallFrame( 1253 const String16& callFrameId, const String16& expression, 1254 Maybe<String16> objectGroup, Maybe<bool> includeCommandLineAPI, 1255 Maybe<bool> silent, Maybe<bool> returnByValue, Maybe<bool> generatePreview, 1256 Maybe<bool> throwOnSideEffect, Maybe<double> timeout, 1257 std::unique_ptr<RemoteObject>* result, 1258 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) { 1259 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); 1260 InjectedScript::CallFrameScope scope(m_session, callFrameId); 1261 Response response = scope.initialize(); 1262 if (!response.IsSuccess()) return response; 1263 if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI(); 1264 if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole(); 1265 1266 int frameOrdinal = static_cast<int>(scope.frameOrdinal()); 1267 auto it = v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal); 1268 if (it->Done()) { 1269 return Response::ServerError("Could not find call frame with given id"); 1270 } 1271 1272 v8::MaybeLocal<v8::Value> maybeResultValue; 1273 { 1274 V8InspectorImpl::EvaluateScope evaluateScope(scope); 1275 if (timeout.isJust()) { 1276 response = evaluateScope.setTimeout(timeout.fromJust() / 1000.0); 1277 if (!response.IsSuccess()) return response; 1278 } 1279 maybeResultValue = it->Evaluate(toV8String(m_isolate, expression), 1280 throwOnSideEffect.fromMaybe(false)); 1281 } 1282 // Re-initialize after running client's code, as it could have destroyed 1283 // context or session. 1284 response = scope.initialize(); 1285 if (!response.IsSuccess()) return response; 1286 WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview 1287 : WrapMode::kNoPreview; 1288 if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue; 1289 return scope.injectedScript()->wrapEvaluateResult( 1290 maybeResultValue, scope.tryCatch(), objectGroup.fromMaybe(""), mode, 1291 result, exceptionDetails); 1292} 1293 1294Response V8DebuggerAgentImpl::setVariableValue( 1295 int scopeNumber, const String16& variableName, 1296 std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument, 1297 const String16& callFrameId) { 1298 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 1299 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); 1300 InjectedScript::CallFrameScope scope(m_session, callFrameId); 1301 Response response = scope.initialize(); 1302 if (!response.IsSuccess()) return response; 1303 v8::Local<v8::Value> newValue; 1304 response = scope.injectedScript()->resolveCallArgument(newValueArgument.get(), 1305 &newValue); 1306 if (!response.IsSuccess()) return response; 1307 1308 int frameOrdinal = static_cast<int>(scope.frameOrdinal()); 1309 auto it = v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal); 1310 if (it->Done()) { 1311 return Response::ServerError("Could not find call frame with given id"); 1312 } 1313 auto scopeIterator = it->GetScopeIterator(); 1314 while (!scopeIterator->Done() && scopeNumber > 0) { 1315 --scopeNumber; 1316 scopeIterator->Advance(); 1317 } 1318 if (scopeNumber != 0) { 1319 return Response::ServerError("Could not find scope with given number"); 1320 } 1321 1322 if (!scopeIterator->SetVariableValue(toV8String(m_isolate, variableName), 1323 newValue) || 1324 scope.tryCatch().HasCaught()) { 1325 return Response::InternalError(); 1326 } 1327 return Response::Success(); 1328} 1329 1330Response V8DebuggerAgentImpl::setReturnValue( 1331 std::unique_ptr<protocol::Runtime::CallArgument> protocolNewValue) { 1332 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); 1333 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); 1334 v8::HandleScope handleScope(m_isolate); 1335 auto iterator = v8::debug::StackTraceIterator::Create(m_isolate); 1336 if (iterator->Done()) { 1337 return Response::ServerError("Could not find top call frame"); 1338 } 1339 if (iterator->GetReturnValue().IsEmpty()) { 1340 return Response::ServerError( 1341 "Could not update return value at non-return position"); 1342 } 1343 InjectedScript::ContextScope scope(m_session, iterator->GetContextId()); 1344 Response response = scope.initialize(); 1345 if (!response.IsSuccess()) return response; 1346 v8::Local<v8::Value> newValue; 1347 response = scope.injectedScript()->resolveCallArgument(protocolNewValue.get(), 1348 &newValue); 1349 if (!response.IsSuccess()) return response; 1350 v8::debug::SetReturnValue(m_isolate, newValue); 1351 return Response::Success(); 1352} 1353 1354Response V8DebuggerAgentImpl::setAsyncCallStackDepth(int depth) { 1355 if (!enabled() && !m_session->runtimeAgent()->enabled()) { 1356 return Response::ServerError(kDebuggerNotEnabled); 1357 } 1358 m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, depth); 1359 m_debugger->setAsyncCallStackDepth(this, depth); 1360 return Response::Success(); 1361} 1362 1363Response V8DebuggerAgentImpl::setBlackboxPatterns( 1364 std::unique_ptr<protocol::Array<String16>> patterns) { 1365 if (patterns->empty()) { 1366 m_blackboxPattern = nullptr; 1367 resetBlackboxedStateCache(); 1368 m_state->remove(DebuggerAgentState::blackboxPattern); 1369 return Response::Success(); 1370 } 1371 1372 String16Builder patternBuilder; 1373 patternBuilder.append('('); 1374 for (size_t i = 0; i < patterns->size() - 1; ++i) { 1375 patternBuilder.append((*patterns)[i]); 1376 patternBuilder.append("|"); 1377 } 1378 patternBuilder.append(patterns->back()); 1379 patternBuilder.append(')'); 1380 String16 pattern = patternBuilder.toString(); 1381 Response response = setBlackboxPattern(pattern); 1382 if (!response.IsSuccess()) return response; 1383 resetBlackboxedStateCache(); 1384 m_state->setString(DebuggerAgentState::blackboxPattern, pattern); 1385 return Response::Success(); 1386} 1387 1388Response V8DebuggerAgentImpl::setBlackboxPattern(const String16& pattern) { 1389 std::unique_ptr<V8Regex> regex(new V8Regex( 1390 m_inspector, pattern, true /** caseSensitive */, false /** multiline */)); 1391 if (!regex->isValid()) 1392 return Response::ServerError("Pattern parser error: " + 1393 regex->errorMessage().utf8()); 1394 m_blackboxPattern = std::move(regex); 1395 return Response::Success(); 1396} 1397 1398void V8DebuggerAgentImpl::resetBlackboxedStateCache() { 1399 for (const auto& it : m_scripts) { 1400 it.second->resetBlackboxedStateCache(); 1401 } 1402} 1403 1404Response V8DebuggerAgentImpl::setBlackboxedRanges( 1405 const String16& scriptId, 1406 std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>> 1407 inPositions) { 1408 auto it = m_scripts.find(scriptId); 1409 if (it == m_scripts.end()) 1410 return Response::ServerError("No script with passed id."); 1411 1412 if (inPositions->empty()) { 1413 m_blackboxedPositions.erase(scriptId); 1414 it->second->resetBlackboxedStateCache(); 1415 return Response::Success(); 1416 } 1417 1418 std::vector<std::pair<int, int>> positions; 1419 positions.reserve(inPositions->size()); 1420 for (const std::unique_ptr<protocol::Debugger::ScriptPosition>& position : 1421 *inPositions) { 1422 Response res = isValidPosition(position.get()); 1423 if (res.IsError()) return res; 1424 1425 positions.push_back( 1426 std::make_pair(position->getLineNumber(), position->getColumnNumber())); 1427 } 1428 Response res = isValidRangeOfPositions(positions); 1429 if (res.IsError()) return res; 1430 1431 m_blackboxedPositions[scriptId] = positions; 1432 it->second->resetBlackboxedStateCache(); 1433 return Response::Success(); 1434} 1435 1436Response V8DebuggerAgentImpl::currentCallFrames( 1437 std::unique_ptr<Array<CallFrame>>* result) { 1438 if (!isPaused()) { 1439 *result = std::make_unique<Array<CallFrame>>(); 1440 return Response::Success(); 1441 } 1442 v8::HandleScope handles(m_isolate); 1443 *result = std::make_unique<Array<CallFrame>>(); 1444 auto iterator = v8::debug::StackTraceIterator::Create(m_isolate); 1445 int frameOrdinal = 0; 1446 for (; !iterator->Done(); iterator->Advance(), frameOrdinal++) { 1447 int contextId = iterator->GetContextId(); 1448 InjectedScript* injectedScript = nullptr; 1449 if (contextId) m_session->findInjectedScript(contextId, injectedScript); 1450 String16 callFrameId = RemoteCallFrameId::serialize( 1451 m_inspector->isolateId(), contextId, frameOrdinal); 1452 1453 v8::debug::Location loc = iterator->GetSourceLocation(); 1454 1455 std::unique_ptr<Array<Scope>> scopes; 1456 auto scopeIterator = iterator->GetScopeIterator(); 1457 Response res = 1458 buildScopes(m_isolate, scopeIterator.get(), injectedScript, &scopes); 1459 if (!res.IsSuccess()) return res; 1460 1461 std::unique_ptr<RemoteObject> protocolReceiver; 1462 if (injectedScript) { 1463 v8::Local<v8::Value> receiver; 1464 if (iterator->GetReceiver().ToLocal(&receiver)) { 1465 res = 1466 injectedScript->wrapObject(receiver, kBacktraceObjectGroup, 1467 WrapMode::kNoPreview, &protocolReceiver); 1468 if (!res.IsSuccess()) return res; 1469 } 1470 } 1471 if (!protocolReceiver) { 1472 protocolReceiver = RemoteObject::create() 1473 .setType(RemoteObject::TypeEnum::Undefined) 1474 .build(); 1475 } 1476 1477 v8::Local<v8::debug::Script> script = iterator->GetScript(); 1478 DCHECK(!script.IsEmpty()); 1479 std::unique_ptr<protocol::Debugger::Location> location = 1480 protocol::Debugger::Location::create() 1481 .setScriptId(String16::fromInteger(script->Id())) 1482 .setLineNumber(loc.GetLineNumber()) 1483 .setColumnNumber(loc.GetColumnNumber()) 1484 .build(); 1485 1486 auto frame = CallFrame::create() 1487 .setCallFrameId(callFrameId) 1488 .setFunctionName(toProtocolString( 1489 m_isolate, iterator->GetFunctionDebugName())) 1490 .setLocation(std::move(location)) 1491 .setUrl(String16()) 1492 .setScopeChain(std::move(scopes)) 1493 .setThis(std::move(protocolReceiver)) 1494 .build(); 1495 1496 v8::Local<v8::Function> func = iterator->GetFunction(); 1497 if (!func.IsEmpty()) { 1498 frame->setFunctionLocation( 1499 protocol::Debugger::Location::create() 1500 .setScriptId(String16::fromInteger(func->ScriptId())) 1501 .setLineNumber(func->GetScriptLineNumber()) 1502 .setColumnNumber(func->GetScriptColumnNumber()) 1503 .build()); 1504 } 1505 1506 v8::Local<v8::Value> returnValue = iterator->GetReturnValue(); 1507 if (!returnValue.IsEmpty() && injectedScript) { 1508 std::unique_ptr<RemoteObject> value; 1509 res = injectedScript->wrapObject(returnValue, kBacktraceObjectGroup, 1510 WrapMode::kNoPreview, &value); 1511 if (!res.IsSuccess()) return res; 1512 frame->setReturnValue(std::move(value)); 1513 } 1514 (*result)->emplace_back(std::move(frame)); 1515 } 1516 return Response::Success(); 1517} 1518 1519std::unique_ptr<protocol::Runtime::StackTrace> 1520V8DebuggerAgentImpl::currentAsyncStackTrace() { 1521 std::shared_ptr<AsyncStackTrace> asyncParent = 1522 m_debugger->currentAsyncParent(); 1523 if (!asyncParent) return nullptr; 1524 return asyncParent->buildInspectorObject( 1525 m_debugger, m_debugger->maxAsyncCallChainDepth() - 1); 1526} 1527 1528std::unique_ptr<protocol::Runtime::StackTraceId> 1529V8DebuggerAgentImpl::currentExternalStackTrace() { 1530 V8StackTraceId externalParent = m_debugger->currentExternalParent(); 1531 if (externalParent.IsInvalid()) return nullptr; 1532 return protocol::Runtime::StackTraceId::create() 1533 .setId(stackTraceIdToString(externalParent.id)) 1534 .setDebuggerId( 1535 internal::V8DebuggerId(externalParent.debugger_id).toString()) 1536 .build(); 1537} 1538 1539bool V8DebuggerAgentImpl::isPaused() const { 1540 return m_debugger->isPausedInContextGroup(m_session->contextGroupId()); 1541} 1542 1543static String16 getScriptLanguage(const V8DebuggerScript& script) { 1544 switch (script.getLanguage()) { 1545 case V8DebuggerScript::Language::WebAssembly: 1546 return protocol::Debugger::ScriptLanguageEnum::WebAssembly; 1547 case V8DebuggerScript::Language::JavaScript: 1548 return protocol::Debugger::ScriptLanguageEnum::JavaScript; 1549 } 1550} 1551 1552#if V8_ENABLE_WEBASSEMBLY 1553static const char* getDebugSymbolTypeName( 1554 v8::debug::WasmScript::DebugSymbolsType type) { 1555 switch (type) { 1556 case v8::debug::WasmScript::DebugSymbolsType::None: 1557 return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum::None; 1558 case v8::debug::WasmScript::DebugSymbolsType::SourceMap: 1559 return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum:: 1560 SourceMap; 1561 case v8::debug::WasmScript::DebugSymbolsType::EmbeddedDWARF: 1562 return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum:: 1563 EmbeddedDWARF; 1564 case v8::debug::WasmScript::DebugSymbolsType::ExternalDWARF: 1565 return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum:: 1566 ExternalDWARF; 1567 } 1568} 1569 1570static std::unique_ptr<protocol::Debugger::DebugSymbols> getDebugSymbols( 1571 const V8DebuggerScript& script) { 1572 v8::debug::WasmScript::DebugSymbolsType type; 1573 if (!script.getDebugSymbolsType().To(&type)) return {}; 1574 1575 std::unique_ptr<protocol::Debugger::DebugSymbols> debugSymbols = 1576 v8_inspector::protocol::Debugger::DebugSymbols::create() 1577 .setType(getDebugSymbolTypeName(type)) 1578 .build(); 1579 String16 externalUrl; 1580 if (script.getExternalDebugSymbolsURL().To(&externalUrl)) { 1581 debugSymbols->setExternalURL(externalUrl); 1582 } 1583 return debugSymbols; 1584} 1585#endif // V8_ENABLE_WEBASSEMBLY 1586 1587void V8DebuggerAgentImpl::didParseSource( 1588 std::unique_ptr<V8DebuggerScript> script, bool success) { 1589 v8::HandleScope handles(m_isolate); 1590 if (!success) { 1591 String16 scriptSource = script->source(0); 1592 script->setSourceURL(findSourceURL(scriptSource, false)); 1593 script->setSourceMappingURL(findSourceMapURL(scriptSource, false)); 1594 } 1595 1596 int contextId = script->executionContextId(); 1597 int contextGroupId = m_inspector->contextGroupId(contextId); 1598 InspectedContext* inspected = 1599 m_inspector->getContext(contextGroupId, contextId); 1600 std::unique_ptr<protocol::DictionaryValue> executionContextAuxData; 1601 if (inspected) { 1602 // Script reused between different groups/sessions can have a stale 1603 // execution context id. 1604 const String16& aux = inspected->auxData(); 1605 std::vector<uint8_t> cbor; 1606 v8_crdtp::json::ConvertJSONToCBOR( 1607 v8_crdtp::span<uint16_t>(aux.characters16(), aux.length()), &cbor); 1608 executionContextAuxData = protocol::DictionaryValue::cast( 1609 protocol::Value::parseBinary(cbor.data(), cbor.size())); 1610 } 1611 bool isLiveEdit = script->isLiveEdit(); 1612 bool hasSourceURLComment = script->hasSourceURLComment(); 1613 bool isModule = script->isModule(); 1614 String16 scriptId = script->scriptId(); 1615 String16 scriptURL = script->sourceURL(); 1616 String16 embedderName = script->embedderName(); 1617 String16 scriptLanguage = getScriptLanguage(*script); 1618 Maybe<int> codeOffset; 1619 std::unique_ptr<protocol::Debugger::DebugSymbols> debugSymbols; 1620#if V8_ENABLE_WEBASSEMBLY 1621 if (script->getLanguage() == V8DebuggerScript::Language::WebAssembly) 1622 codeOffset = script->codeOffset(); 1623 debugSymbols = getDebugSymbols(*script); 1624#endif // V8_ENABLE_WEBASSEMBLY 1625 1626 m_scripts[scriptId] = std::move(script); 1627 // Release the strong reference to get notified when debugger is the only 1628 // one that holds the script. Has to be done after script added to m_scripts. 1629 m_scripts[scriptId]->MakeWeak(); 1630 1631 ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId); 1632 DCHECK(scriptIterator != m_scripts.end()); 1633 V8DebuggerScript* scriptRef = scriptIterator->second.get(); 1634 // V8 could create functions for parsed scripts before reporting and asks 1635 // inspector about blackboxed state, we should reset state each time when we 1636 // make any change that change isFunctionBlackboxed output - adding parsed 1637 // script is changing. 1638 scriptRef->resetBlackboxedStateCache(); 1639 1640 Maybe<String16> sourceMapURLParam = scriptRef->sourceMappingURL(); 1641 Maybe<protocol::DictionaryValue> executionContextAuxDataParam( 1642 std::move(executionContextAuxData)); 1643 const bool* isLiveEditParam = isLiveEdit ? &isLiveEdit : nullptr; 1644 const bool* hasSourceURLParam = 1645 hasSourceURLComment ? &hasSourceURLComment : nullptr; 1646 const bool* isModuleParam = isModule ? &isModule : nullptr; 1647 std::unique_ptr<V8StackTraceImpl> stack = 1648 V8StackTraceImpl::capture(m_inspector->debugger(), 1); 1649 std::unique_ptr<protocol::Runtime::StackTrace> stackTrace = 1650 stack && !stack->isEmpty() 1651 ? stack->buildInspectorObjectImpl(m_debugger, 0) 1652 : nullptr; 1653 1654 if (!success) { 1655 m_frontend.scriptFailedToParse( 1656 scriptId, scriptURL, scriptRef->startLine(), scriptRef->startColumn(), 1657 scriptRef->endLine(), scriptRef->endColumn(), contextId, 1658 scriptRef->hash(), std::move(executionContextAuxDataParam), 1659 std::move(sourceMapURLParam), hasSourceURLParam, isModuleParam, 1660 scriptRef->length(), std::move(stackTrace), std::move(codeOffset), 1661 std::move(scriptLanguage), embedderName); 1662 return; 1663 } 1664 1665 m_frontend.scriptParsed( 1666 scriptId, scriptURL, scriptRef->startLine(), scriptRef->startColumn(), 1667 scriptRef->endLine(), scriptRef->endColumn(), contextId, 1668 scriptRef->hash(), std::move(executionContextAuxDataParam), 1669 isLiveEditParam, std::move(sourceMapURLParam), hasSourceURLParam, 1670 isModuleParam, scriptRef->length(), std::move(stackTrace), 1671 std::move(codeOffset), std::move(scriptLanguage), std::move(debugSymbols), 1672 embedderName); 1673 1674 std::vector<protocol::DictionaryValue*> potentialBreakpoints; 1675 if (!scriptURL.isEmpty()) { 1676 protocol::DictionaryValue* breakpointsByUrl = 1677 m_state->getObject(DebuggerAgentState::breakpointsByUrl); 1678 if (breakpointsByUrl) { 1679 potentialBreakpoints.push_back(breakpointsByUrl->getObject(scriptURL)); 1680 } 1681 potentialBreakpoints.push_back( 1682 m_state->getObject(DebuggerAgentState::breakpointsByRegex)); 1683 } 1684 protocol::DictionaryValue* breakpointsByScriptHash = 1685 m_state->getObject(DebuggerAgentState::breakpointsByScriptHash); 1686 if (breakpointsByScriptHash) { 1687 potentialBreakpoints.push_back( 1688 breakpointsByScriptHash->getObject(scriptRef->hash())); 1689 } 1690 protocol::DictionaryValue* breakpointHints = 1691 m_state->getObject(DebuggerAgentState::breakpointHints); 1692 for (auto breakpoints : potentialBreakpoints) { 1693 if (!breakpoints) continue; 1694 for (size_t i = 0; i < breakpoints->size(); ++i) { 1695 auto breakpointWithCondition = breakpoints->at(i); 1696 String16 breakpointId = breakpointWithCondition.first; 1697 1698 BreakpointType type; 1699 String16 selector; 1700 int lineNumber = 0; 1701 int columnNumber = 0; 1702 parseBreakpointId(breakpointId, &type, &selector, &lineNumber, 1703 &columnNumber); 1704 1705 if (!matches(m_inspector, *scriptRef, type, selector)) continue; 1706 String16 condition; 1707 breakpointWithCondition.second->asString(&condition); 1708 String16 hint; 1709 bool hasHint = 1710 breakpointHints && breakpointHints->getString(breakpointId, &hint); 1711 if (hasHint) { 1712 adjustBreakpointLocation(*scriptRef, hint, &lineNumber, &columnNumber); 1713 } 1714 std::unique_ptr<protocol::Debugger::Location> location = 1715 setBreakpointImpl(breakpointId, scriptId, condition, lineNumber, 1716 columnNumber); 1717 if (location) 1718 m_frontend.breakpointResolved(breakpointId, std::move(location)); 1719 } 1720 } 1721 setScriptInstrumentationBreakpointIfNeeded(scriptRef); 1722} 1723 1724void V8DebuggerAgentImpl::setScriptInstrumentationBreakpointIfNeeded( 1725 V8DebuggerScript* scriptRef) { 1726 protocol::DictionaryValue* breakpoints = 1727 m_state->getObject(DebuggerAgentState::instrumentationBreakpoints); 1728 if (!breakpoints) return; 1729 bool isBlackboxed = isFunctionBlackboxed( 1730 scriptRef->scriptId(), v8::debug::Location(0, 0), 1731 v8::debug::Location(scriptRef->endLine(), scriptRef->endColumn())); 1732 if (isBlackboxed) return; 1733 1734 String16 sourceMapURL = scriptRef->sourceMappingURL(); 1735 String16 breakpointId = generateInstrumentationBreakpointId( 1736 InstrumentationEnum::BeforeScriptExecution); 1737 if (!breakpoints->get(breakpointId)) { 1738 if (sourceMapURL.isEmpty()) return; 1739 breakpointId = generateInstrumentationBreakpointId( 1740 InstrumentationEnum::BeforeScriptWithSourceMapExecution); 1741 if (!breakpoints->get(breakpointId)) return; 1742 } 1743 v8::debug::BreakpointId debuggerBreakpointId; 1744 if (!scriptRef->setInstrumentationBreakpoint(&debuggerBreakpointId)) return; 1745 1746 m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId; 1747 m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back( 1748 debuggerBreakpointId); 1749} 1750 1751void V8DebuggerAgentImpl::didPauseOnInstrumentation( 1752 v8::debug::BreakpointId instrumentationId) { 1753 String16 breakReason = protocol::Debugger::Paused::ReasonEnum::Other; 1754 std::unique_ptr<protocol::DictionaryValue> breakAuxData; 1755 1756 std::unique_ptr<Array<CallFrame>> protocolCallFrames; 1757 Response response = currentCallFrames(&protocolCallFrames); 1758 if (!response.IsSuccess()) 1759 protocolCallFrames = std::make_unique<Array<CallFrame>>(); 1760 1761 if (m_debuggerBreakpointIdToBreakpointId.find(instrumentationId) != 1762 m_debuggerBreakpointIdToBreakpointId.end()) { 1763 DCHECK_GT(protocolCallFrames->size(), 0); 1764 if (protocolCallFrames->size() > 0) { 1765 breakReason = protocol::Debugger::Paused::ReasonEnum::Instrumentation; 1766 const String16 scriptId = 1767 protocolCallFrames->at(0)->getLocation()->getScriptId(); 1768 DCHECK_NE(m_scripts.find(scriptId), m_scripts.end()); 1769 const auto& script = m_scripts[scriptId]; 1770 1771 breakAuxData = protocol::DictionaryValue::create(); 1772 breakAuxData->setString("scriptId", script->scriptId()); 1773 breakAuxData->setString("url", script->sourceURL()); 1774 if (!script->sourceMappingURL().isEmpty()) { 1775 breakAuxData->setString("sourceMapURL", (script->sourceMappingURL())); 1776 } 1777 } 1778 } 1779 1780 m_frontend.paused(std::move(protocolCallFrames), breakReason, 1781 std::move(breakAuxData), 1782 std::make_unique<Array<String16>>(), 1783 currentAsyncStackTrace(), currentExternalStackTrace()); 1784} 1785 1786void V8DebuggerAgentImpl::didPause( 1787 int contextId, v8::Local<v8::Value> exception, 1788 const std::vector<v8::debug::BreakpointId>& hitBreakpoints, 1789 v8::debug::ExceptionType exceptionType, bool isUncaught, 1790 v8::debug::BreakReasons breakReasons) { 1791 v8::HandleScope handles(m_isolate); 1792 1793 std::vector<BreakReason> hitReasons; 1794 1795 if (breakReasons.contains(v8::debug::BreakReason::kOOM)) { 1796 hitReasons.push_back( 1797 std::make_pair(protocol::Debugger::Paused::ReasonEnum::OOM, nullptr)); 1798 } else if (breakReasons.contains(v8::debug::BreakReason::kAssert)) { 1799 hitReasons.push_back(std::make_pair( 1800 protocol::Debugger::Paused::ReasonEnum::Assert, nullptr)); 1801 } else if (breakReasons.contains(v8::debug::BreakReason::kException)) { 1802 InjectedScript* injectedScript = nullptr; 1803 m_session->findInjectedScript(contextId, injectedScript); 1804 if (injectedScript) { 1805 String16 breakReason = 1806 exceptionType == v8::debug::kPromiseRejection 1807 ? protocol::Debugger::Paused::ReasonEnum::PromiseRejection 1808 : protocol::Debugger::Paused::ReasonEnum::Exception; 1809 std::unique_ptr<protocol::Runtime::RemoteObject> obj; 1810 injectedScript->wrapObject(exception, kBacktraceObjectGroup, 1811 WrapMode::kNoPreview, &obj); 1812 std::unique_ptr<protocol::DictionaryValue> breakAuxData; 1813 if (obj) { 1814 std::vector<uint8_t> serialized; 1815 obj->AppendSerialized(&serialized); 1816 breakAuxData = protocol::DictionaryValue::cast( 1817 protocol::Value::parseBinary(serialized.data(), serialized.size())); 1818 breakAuxData->setBoolean("uncaught", isUncaught); 1819 } 1820 hitReasons.push_back( 1821 std::make_pair(breakReason, std::move(breakAuxData))); 1822 } 1823 } 1824 1825 auto hitBreakpointIds = std::make_unique<Array<String16>>(); 1826 bool hitRegularBreakpoint = false; 1827 for (const auto& id : hitBreakpoints) { 1828 auto breakpointIterator = m_debuggerBreakpointIdToBreakpointId.find(id); 1829 if (breakpointIterator == m_debuggerBreakpointIdToBreakpointId.end()) { 1830 continue; 1831 } 1832 const String16& breakpointId = breakpointIterator->second; 1833 hitBreakpointIds->emplace_back(breakpointId); 1834 BreakpointType type; 1835 parseBreakpointId(breakpointId, &type); 1836 if (type == BreakpointType::kDebugCommand) { 1837 hitReasons.push_back(std::make_pair( 1838 protocol::Debugger::Paused::ReasonEnum::DebugCommand, nullptr)); 1839 } else { 1840 hitRegularBreakpoint = true; 1841 } 1842 } 1843 1844 for (size_t i = 0; i < m_breakReason.size(); ++i) { 1845 hitReasons.push_back(std::move(m_breakReason[i])); 1846 } 1847 clearBreakDetails(); 1848 1849 // Make sure that we only include (other: nullptr) once. 1850 const BreakReason otherHitReason = 1851 std::make_pair(protocol::Debugger::Paused::ReasonEnum::Other, nullptr); 1852 const bool otherBreakReasons = 1853 hitRegularBreakpoint || hitBreakReasonEncodedAsOther(breakReasons); 1854 if (otherBreakReasons && std::find(hitReasons.begin(), hitReasons.end(), 1855 otherHitReason) == hitReasons.end()) { 1856 hitReasons.push_back( 1857 std::make_pair(protocol::Debugger::Paused::ReasonEnum::Other, nullptr)); 1858 } 1859 1860 // We should always know why we pause: either the pause relates to this agent 1861 // (`hitReason` is non empty), or it relates to another agent (hit a 1862 // breakpoint there, or a triggered pause was scheduled by other agent). 1863 DCHECK(hitReasons.size() > 0 || !hitBreakpoints.empty() || 1864 breakReasons.contains(v8::debug::BreakReason::kAgent)); 1865 String16 breakReason = protocol::Debugger::Paused::ReasonEnum::Other; 1866 std::unique_ptr<protocol::DictionaryValue> breakAuxData; 1867 if (hitReasons.size() == 1) { 1868 breakReason = hitReasons[0].first; 1869 breakAuxData = std::move(hitReasons[0].second); 1870 } else if (hitReasons.size() > 1) { 1871 breakReason = protocol::Debugger::Paused::ReasonEnum::Ambiguous; 1872 std::unique_ptr<protocol::ListValue> reasons = 1873 protocol::ListValue::create(); 1874 for (size_t i = 0; i < hitReasons.size(); ++i) { 1875 std::unique_ptr<protocol::DictionaryValue> reason = 1876 protocol::DictionaryValue::create(); 1877 reason->setString("reason", hitReasons[i].first); 1878 if (hitReasons[i].second) 1879 reason->setObject("auxData", std::move(hitReasons[i].second)); 1880 reasons->pushValue(std::move(reason)); 1881 } 1882 breakAuxData = protocol::DictionaryValue::create(); 1883 breakAuxData->setArray("reasons", std::move(reasons)); 1884 } 1885 1886 std::unique_ptr<Array<CallFrame>> protocolCallFrames; 1887 Response response = currentCallFrames(&protocolCallFrames); 1888 if (!response.IsSuccess()) 1889 protocolCallFrames = std::make_unique<Array<CallFrame>>(); 1890 1891 m_frontend.paused(std::move(protocolCallFrames), breakReason, 1892 std::move(breakAuxData), std::move(hitBreakpointIds), 1893 currentAsyncStackTrace(), currentExternalStackTrace()); 1894} 1895 1896void V8DebuggerAgentImpl::didContinue() { 1897 m_frontend.resumed(); 1898 m_frontend.flush(); 1899} 1900 1901void V8DebuggerAgentImpl::breakProgram( 1902 const String16& breakReason, 1903 std::unique_ptr<protocol::DictionaryValue> data) { 1904 if (!enabled() || m_skipAllPauses || !m_debugger->canBreakProgram()) return; 1905 std::vector<BreakReason> currentScheduledReason; 1906 currentScheduledReason.swap(m_breakReason); 1907 pushBreakDetails(breakReason, std::move(data)); 1908 1909 int contextGroupId = m_session->contextGroupId(); 1910 int sessionId = m_session->sessionId(); 1911 V8InspectorImpl* inspector = m_inspector; 1912 m_debugger->breakProgram(contextGroupId); 1913 // Check that session and |this| are still around. 1914 if (!inspector->sessionById(contextGroupId, sessionId)) return; 1915 if (!enabled()) return; 1916 1917 popBreakDetails(); 1918 m_breakReason.swap(currentScheduledReason); 1919 if (!m_breakReason.empty()) { 1920 m_debugger->setPauseOnNextCall(true, m_session->contextGroupId()); 1921 } 1922} 1923 1924void V8DebuggerAgentImpl::setBreakpointFor(v8::Local<v8::Function> function, 1925 v8::Local<v8::String> condition, 1926 BreakpointSource source) { 1927 String16 breakpointId = generateBreakpointId( 1928 source == DebugCommandBreakpointSource ? BreakpointType::kDebugCommand 1929 : BreakpointType::kMonitorCommand, 1930 function); 1931 if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) != 1932 m_breakpointIdToDebuggerBreakpointIds.end()) { 1933 return; 1934 } 1935 setBreakpointImpl(breakpointId, function, condition); 1936} 1937 1938void V8DebuggerAgentImpl::removeBreakpointFor(v8::Local<v8::Function> function, 1939 BreakpointSource source) { 1940 String16 breakpointId = generateBreakpointId( 1941 source == DebugCommandBreakpointSource ? BreakpointType::kDebugCommand 1942 : BreakpointType::kMonitorCommand, 1943 function); 1944 std::vector<V8DebuggerScript*> scripts; 1945 removeBreakpointImpl(breakpointId, scripts); 1946} 1947 1948void V8DebuggerAgentImpl::reset() { 1949 if (!enabled()) return; 1950 m_blackboxedPositions.clear(); 1951 resetBlackboxedStateCache(); 1952 m_skipList.clear(); 1953 m_scripts.clear(); 1954 m_cachedScripts.clear(); 1955 m_cachedScriptSize = 0; 1956} 1957 1958void V8DebuggerAgentImpl::ScriptCollected(const V8DebuggerScript* script) { 1959 DCHECK_NE(m_scripts.find(script->scriptId()), m_scripts.end()); 1960 std::vector<uint8_t> bytecode; 1961#if V8_ENABLE_WEBASSEMBLY 1962 v8::MemorySpan<const uint8_t> span; 1963 if (script->wasmBytecode().To(&span)) { 1964 bytecode.reserve(span.size()); 1965 bytecode.insert(bytecode.begin(), span.data(), span.data() + span.size()); 1966 } 1967#endif 1968 CachedScript cachedScript{script->scriptId(), script->source(0), 1969 std::move(bytecode)}; 1970 m_cachedScriptSize += cachedScript.size(); 1971 m_cachedScripts.push_back(std::move(cachedScript)); 1972 m_scripts.erase(script->scriptId()); 1973 1974 while (m_cachedScriptSize > m_maxScriptCacheSize) { 1975 const CachedScript& cachedScript = m_cachedScripts.front(); 1976 DCHECK_GE(m_cachedScriptSize, cachedScript.size()); 1977 m_cachedScriptSize -= cachedScript.size(); 1978 m_cachedScripts.pop_front(); 1979 } 1980} 1981 1982Response V8DebuggerAgentImpl::processSkipList( 1983 protocol::Array<protocol::Debugger::LocationRange>* skipList) { 1984 std::unordered_map<String16, std::vector<std::pair<int, int>>> skipListInit; 1985 for (std::unique_ptr<protocol::Debugger::LocationRange>& range : *skipList) { 1986 protocol::Debugger::ScriptPosition* start = range->getStart(); 1987 protocol::Debugger::ScriptPosition* end = range->getEnd(); 1988 String16 scriptId = range->getScriptId(); 1989 1990 auto it = m_scripts.find(scriptId); 1991 if (it == m_scripts.end()) 1992 return Response::ServerError("No script with passed id."); 1993 1994 Response res = isValidPosition(start); 1995 if (res.IsError()) return res; 1996 1997 res = isValidPosition(end); 1998 if (res.IsError()) return res; 1999 2000 skipListInit[scriptId].emplace_back(start->getLineNumber(), 2001 start->getColumnNumber()); 2002 skipListInit[scriptId].emplace_back(end->getLineNumber(), 2003 end->getColumnNumber()); 2004 } 2005 2006 // Verify that the skipList is sorted, and that all ranges 2007 // are properly defined (start comes before end). 2008 for (auto skipListPair : skipListInit) { 2009 Response res = isValidRangeOfPositions(skipListPair.second); 2010 if (res.IsError()) return res; 2011 } 2012 2013 m_skipList = std::move(skipListInit); 2014 return Response::Success(); 2015} 2016} // namespace v8_inspector 2017