| // Copyright 2015 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/inspector/v8-debugger-agent-impl.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "../../third_party/inspector_protocol/crdtp/json.h" |
| #include "include/v8-context.h" |
| #include "include/v8-function.h" |
| #include "include/v8-inspector.h" |
| #include "include/v8-microtask-queue.h" |
| #include "src/base/safe_conversions.h" |
| #include "src/debug/debug-interface.h" |
| #include "src/inspector/crc32.h" |
| #include "src/inspector/injected-script.h" |
| #include "src/inspector/inspected-context.h" |
| #include "src/inspector/protocol/Debugger.h" |
| #include "src/inspector/protocol/Protocol.h" |
| #include "src/inspector/remote-object-id.h" |
| #include "src/inspector/search-util.h" |
| #include "src/inspector/string-util.h" |
| #include "src/inspector/v8-debugger-script.h" |
| #include "src/inspector/v8-debugger.h" |
| #include "src/inspector/v8-inspector-impl.h" |
| #include "src/inspector/v8-inspector-session-impl.h" |
| #include "src/inspector/v8-regex.h" |
| #include "src/inspector/v8-runtime-agent-impl.h" |
| #include "src/inspector/v8-stack-trace-impl.h" |
| #include "src/inspector/v8-value-utils.h" |
| |
| namespace v8_inspector { |
| |
| using protocol::Array; |
| using protocol::Maybe; |
| using protocol::Debugger::BreakpointId; |
| using protocol::Debugger::CallFrame; |
| using protocol::Debugger::Scope; |
| using protocol::Runtime::ExceptionDetails; |
| using protocol::Runtime::RemoteObject; |
| using protocol::Runtime::ScriptId; |
| |
| namespace InstrumentationEnum = |
| protocol::Debugger::SetInstrumentationBreakpoint::InstrumentationEnum; |
| |
| namespace DebuggerAgentState { |
| static const char pauseOnExceptionsState[] = "pauseOnExceptionsState"; |
| static const char asyncCallStackDepth[] = "asyncCallStackDepth"; |
| static const char blackboxPattern[] = "blackboxPattern"; |
| static const char debuggerEnabled[] = "debuggerEnabled"; |
| static const char breakpointsActiveWhenEnabled[] = "breakpointsActive"; |
| static const char skipAllPauses[] = "skipAllPauses"; |
| |
| static const char breakpointsByRegex[] = "breakpointsByRegex"; |
| static const char breakpointsByUrl[] = "breakpointsByUrl"; |
| static const char breakpointsByScriptHash[] = "breakpointsByScriptHash"; |
| static const char breakpointHints[] = "breakpointHints"; |
| static const char breakpointHintText[] = "text"; |
| static const char breakpointHintPrefixHash[] = "prefixHash"; |
| static const char breakpointHintPrefixLength[] = "prefixLen"; |
| static const char instrumentationBreakpoints[] = "instrumentationBreakpoints"; |
| static const char maxScriptCacheSize[] = "maxScriptCacheSize"; |
| |
| } // namespace DebuggerAgentState |
| |
| static const char kBacktraceObjectGroup[] = "backtrace"; |
| static const char kDebuggerNotEnabled[] = "Debugger agent is not enabled"; |
| static const char kDebuggerNotPaused[] = |
| "Can only perform operation while paused."; |
| |
| static const size_t kBreakpointHintMaxLength = 128; |
| static const intptr_t kBreakpointHintMaxSearchOffset = 80 * 10; |
| // Limit the number of breakpoints returned, as we otherwise may exceed |
| // the maximum length of a message in mojo (see https://crbug.com/1105172). |
| static const size_t kMaxNumBreakpoints = 1000; |
| |
| #if V8_ENABLE_WEBASSEMBLY |
| // TODO(1099680): getScriptSource and getWasmBytecode return Wasm wire bytes |
| // as protocol::Binary, which is encoded as JSON string in the communication |
| // to the DevTools front-end and hence leads to either crashing the renderer |
| // that is being debugged or the renderer that's running the front-end if we |
| // allow arbitrarily big Wasm byte sequences here. Ideally we would find a |
| // different way to transfer the wire bytes (middle- to long-term), but as a |
| // short-term solution, we should at least not crash. |
| static constexpr size_t kWasmBytecodeMaxLength = |
| (v8::String::kMaxLength / 4) * 3; |
| static constexpr const char kWasmBytecodeExceedsTransferLimit[] = |
| "WebAssembly bytecode exceeds the transfer limit"; |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| namespace { |
| |
| enum class BreakpointType { |
| kByUrl = 1, |
| kByUrlRegex, |
| kByScriptHash, |
| kByScriptId, |
| kDebugCommand, |
| kMonitorCommand, |
| kBreakpointAtEntry, |
| kInstrumentationBreakpoint |
| }; |
| |
| String16 generateBreakpointId(BreakpointType type, |
| const String16& scriptSelector, int lineNumber, |
| int columnNumber) { |
| String16Builder builder; |
| builder.appendNumber(static_cast<int>(type)); |
| builder.append(':'); |
| builder.appendNumber(lineNumber); |
| builder.append(':'); |
| builder.appendNumber(columnNumber); |
| builder.append(':'); |
| builder.append(scriptSelector); |
| return builder.toString(); |
| } |
| |
| String16 generateBreakpointId(BreakpointType type, |
| v8::Local<v8::Function> function) { |
| String16Builder builder; |
| builder.appendNumber(static_cast<int>(type)); |
| builder.append(':'); |
| builder.appendNumber(v8::debug::GetDebuggingId(function)); |
| return builder.toString(); |
| } |
| |
| String16 generateInstrumentationBreakpointId(const String16& instrumentation) { |
| String16Builder builder; |
| builder.appendNumber( |
| static_cast<int>(BreakpointType::kInstrumentationBreakpoint)); |
| builder.append(':'); |
| builder.append(instrumentation); |
| return builder.toString(); |
| } |
| |
| bool parseBreakpointId(const String16& breakpointId, BreakpointType* type, |
| String16* scriptSelector = nullptr, |
| int* lineNumber = nullptr, int* columnNumber = nullptr) { |
| size_t typeLineSeparator = breakpointId.find(':'); |
| if (typeLineSeparator == String16::kNotFound) return false; |
| |
| int rawType = breakpointId.substring(0, typeLineSeparator).toInteger(); |
| if (rawType < static_cast<int>(BreakpointType::kByUrl) || |
| rawType > static_cast<int>(BreakpointType::kInstrumentationBreakpoint)) { |
| return false; |
| } |
| if (type) *type = static_cast<BreakpointType>(rawType); |
| if (rawType == static_cast<int>(BreakpointType::kDebugCommand) || |
| rawType == static_cast<int>(BreakpointType::kMonitorCommand) || |
| rawType == static_cast<int>(BreakpointType::kBreakpointAtEntry) || |
| rawType == static_cast<int>(BreakpointType::kInstrumentationBreakpoint)) { |
| // The script and source position are not encoded in this case. |
| return true; |
| } |
| |
| size_t lineColumnSeparator = breakpointId.find(':', typeLineSeparator + 1); |
| if (lineColumnSeparator == String16::kNotFound) return false; |
| size_t columnSelectorSeparator = |
| breakpointId.find(':', lineColumnSeparator + 1); |
| if (columnSelectorSeparator == String16::kNotFound) return false; |
| if (scriptSelector) { |
| *scriptSelector = breakpointId.substring(columnSelectorSeparator + 1); |
| } |
| if (lineNumber) { |
| *lineNumber = breakpointId |
| .substring(typeLineSeparator + 1, |
| lineColumnSeparator - typeLineSeparator - 1) |
| .toInteger(); |
| } |
| if (columnNumber) { |
| *columnNumber = |
| breakpointId |
| .substring(lineColumnSeparator + 1, |
| columnSelectorSeparator - lineColumnSeparator - 1) |
| .toInteger(); |
| } |
| return true; |
| } |
| |
| bool positionComparator(const std::pair<int, int>& a, |
| const std::pair<int, int>& b) { |
| if (a.first != b.first) return a.first < b.first; |
| return a.second < b.second; |
| } |
| |
| std::unique_ptr<protocol::DictionaryValue> breakpointHint( |
| const V8DebuggerScript& script, int breakpointLineNumber, |
| int breakpointColumnNumber, int actualLineNumber, int actualColumnNumber) { |
| int actualOffset; |
| int breakpointOffset; |
| if (!script.offset(actualLineNumber, actualColumnNumber).To(&actualOffset) || |
| !script.offset(breakpointLineNumber, breakpointColumnNumber) |
| .To(&breakpointOffset)) { |
| return {}; |
| } |
| |
| auto hintObject = protocol::DictionaryValue::create(); |
| String16 rawHint = script.source(actualOffset, kBreakpointHintMaxLength); |
| std::pair<size_t, size_t> offsetAndLength = |
| rawHint.getTrimmedOffsetAndLength(); |
| String16 hint = |
| rawHint.substring(offsetAndLength.first, offsetAndLength.second); |
| for (size_t i = 0; i < hint.length(); ++i) { |
| if (hint[i] == '\r' || hint[i] == '\n' || hint[i] == ';') { |
| hint = hint.substring(0, i); |
| break; |
| } |
| } |
| hintObject->setString(DebuggerAgentState::breakpointHintText, hint); |
| |
| // Also store the hash of the text between the requested breakpoint location |
| // and the actual breakpoint location. If we see the same prefix text next |
| // time, we will keep the breakpoint at the same location (so that |
| // breakpoints do not slide around on reloads without any edits). |
| if (breakpointOffset <= actualOffset) { |
| size_t length = actualOffset - breakpointOffset + offsetAndLength.first; |
| String16 prefix = script.source(breakpointOffset, length); |
| int crc32 = computeCrc32(prefix); |
| hintObject->setInteger(DebuggerAgentState::breakpointHintPrefixHash, crc32); |
| hintObject->setInteger(DebuggerAgentState::breakpointHintPrefixLength, |
| v8::base::checked_cast<int32_t>(length)); |
| } |
| return hintObject; |
| } |
| |
| void adjustBreakpointLocation(const V8DebuggerScript& script, |
| const protocol::DictionaryValue* hintObject, |
| int* lineNumber, int* columnNumber) { |
| if (*lineNumber < script.startLine() || *lineNumber > script.endLine()) |
| return; |
| if (*lineNumber == script.startLine() && |
| *columnNumber < script.startColumn()) { |
| return; |
| } |
| if (*lineNumber == script.endLine() && script.endColumn() < *columnNumber) { |
| return; |
| } |
| |
| int sourceOffset; |
| if (!script.offset(*lineNumber, *columnNumber).To(&sourceOffset)) return; |
| |
| int prefixLength = 0; |
| hintObject->getInteger(DebuggerAgentState::breakpointHintPrefixLength, |
| &prefixLength); |
| String16 hint; |
| if (!hintObject->getString(DebuggerAgentState::breakpointHintText, &hint) || |
| hint.isEmpty()) |
| return; |
| |
| intptr_t searchRegionOffset = std::max( |
| sourceOffset - kBreakpointHintMaxSearchOffset, static_cast<intptr_t>(0)); |
| size_t offset = sourceOffset - searchRegionOffset; |
| size_t searchRegionSize = |
| offset + std::max(kBreakpointHintMaxSearchOffset, |
| static_cast<intptr_t>(prefixLength + hint.length())); |
| |
| String16 searchArea = script.source(searchRegionOffset, searchRegionSize); |
| |
| // Let us see if the breakpoint hint text appears at the same location |
| // as before, with the same prefix text in between. If yes, then we just use |
| // that position. |
| int prefixHash; |
| if (hintObject->getInteger(DebuggerAgentState::breakpointHintPrefixHash, |
| &prefixHash) && |
| offset + prefixLength + hint.length() <= searchArea.length() && |
| searchArea.substring(offset + prefixLength, hint.length()) == hint && |
| computeCrc32(searchArea.substring(offset, prefixLength)) == prefixHash) { |
| v8::debug::Location hintPosition = script.location( |
| static_cast<int>(searchRegionOffset + offset + prefixLength)); |
| *lineNumber = hintPosition.GetLineNumber(); |
| *columnNumber = hintPosition.GetColumnNumber(); |
| return; |
| } |
| |
| size_t nextMatch = searchArea.find(hint, offset); |
| size_t prevMatch = searchArea.reverseFind(hint, offset); |
| if (nextMatch == String16::kNotFound && prevMatch == String16::kNotFound) { |
| return; |
| } |
| size_t bestMatch; |
| if (nextMatch == String16::kNotFound || |
| nextMatch > offset + kBreakpointHintMaxSearchOffset) { |
| bestMatch = prevMatch; |
| } else if (prevMatch == String16::kNotFound) { |
| bestMatch = nextMatch; |
| } else { |
| bestMatch = nextMatch - offset < offset - prevMatch ? nextMatch : prevMatch; |
| } |
| bestMatch += searchRegionOffset; |
| v8::debug::Location hintPosition = |
| script.location(static_cast<int>(bestMatch)); |
| if (hintPosition.IsEmpty()) return; |
| *lineNumber = hintPosition.GetLineNumber(); |
| *columnNumber = hintPosition.GetColumnNumber(); |
| } |
| |
| String16 breakLocationType(v8::debug::BreakLocationType type) { |
| switch (type) { |
| case v8::debug::kCallBreakLocation: |
| return protocol::Debugger::BreakLocation::TypeEnum::Call; |
| case v8::debug::kReturnBreakLocation: |
| return protocol::Debugger::BreakLocation::TypeEnum::Return; |
| case v8::debug::kDebuggerStatementBreakLocation: |
| return protocol::Debugger::BreakLocation::TypeEnum::DebuggerStatement; |
| case v8::debug::kCommonBreakLocation: |
| return String16(); |
| } |
| return String16(); |
| } |
| |
| String16 scopeType(v8::debug::ScopeIterator::ScopeType type) { |
| switch (type) { |
| case v8::debug::ScopeIterator::ScopeTypeGlobal: |
| return Scope::TypeEnum::Global; |
| case v8::debug::ScopeIterator::ScopeTypeLocal: |
| return Scope::TypeEnum::Local; |
| case v8::debug::ScopeIterator::ScopeTypeWith: |
| return Scope::TypeEnum::With; |
| case v8::debug::ScopeIterator::ScopeTypeClosure: |
| return Scope::TypeEnum::Closure; |
| case v8::debug::ScopeIterator::ScopeTypeCatch: |
| return Scope::TypeEnum::Catch; |
| case v8::debug::ScopeIterator::ScopeTypeBlock: |
| return Scope::TypeEnum::Block; |
| case v8::debug::ScopeIterator::ScopeTypeScript: |
| return Scope::TypeEnum::Script; |
| case v8::debug::ScopeIterator::ScopeTypeEval: |
| return Scope::TypeEnum::Eval; |
| case v8::debug::ScopeIterator::ScopeTypeModule: |
| return Scope::TypeEnum::Module; |
| case v8::debug::ScopeIterator::ScopeTypeWasmExpressionStack: |
| return Scope::TypeEnum::WasmExpressionStack; |
| } |
| UNREACHABLE(); |
| } |
| |
| Response buildScopes(v8::Isolate* isolate, v8::debug::ScopeIterator* iterator, |
| InjectedScript* injectedScript, |
| std::unique_ptr<Array<Scope>>* scopes) { |
| *scopes = std::make_unique<Array<Scope>>(); |
| if (!injectedScript) return Response::Success(); |
| if (iterator->Done()) return Response::Success(); |
| |
| String16 scriptId = String16::fromInteger(iterator->GetScriptId()); |
| |
| for (; !iterator->Done(); iterator->Advance()) { |
| std::unique_ptr<RemoteObject> object; |
| Response result = |
| injectedScript->wrapObject(iterator->GetObject(), kBacktraceObjectGroup, |
| WrapOptions({WrapMode::kIdOnly}), &object); |
| if (!result.IsSuccess()) return result; |
| |
| auto scope = Scope::create() |
| .setType(scopeType(iterator->GetType())) |
| .setObject(std::move(object)) |
| .build(); |
| |
| String16 name = toProtocolStringWithTypeCheck( |
| isolate, iterator->GetFunctionDebugName()); |
| if (!name.isEmpty()) scope->setName(name); |
| |
| if (iterator->HasLocationInfo()) { |
| v8::debug::Location start = iterator->GetStartLocation(); |
| scope->setStartLocation(protocol::Debugger::Location::create() |
| .setScriptId(scriptId) |
| .setLineNumber(start.GetLineNumber()) |
| .setColumnNumber(start.GetColumnNumber()) |
| .build()); |
| |
| v8::debug::Location end = iterator->GetEndLocation(); |
| scope->setEndLocation(protocol::Debugger::Location::create() |
| .setScriptId(scriptId) |
| .setLineNumber(end.GetLineNumber()) |
| .setColumnNumber(end.GetColumnNumber()) |
| .build()); |
| } |
| (*scopes)->emplace_back(std::move(scope)); |
| } |
| return Response::Success(); |
| } |
| |
| protocol::DictionaryValue* getOrCreateObject(protocol::DictionaryValue* object, |
| const String16& key) { |
| protocol::DictionaryValue* value = object->getObject(key); |
| if (value) return value; |
| std::unique_ptr<protocol::DictionaryValue> newDictionary = |
| protocol::DictionaryValue::create(); |
| value = newDictionary.get(); |
| object->setObject(key, std::move(newDictionary)); |
| return value; |
| } |
| |
| Response isValidPosition(protocol::Debugger::ScriptPosition* position) { |
| if (position->getLineNumber() < 0) |
| return Response::ServerError("Position missing 'line' or 'line' < 0."); |
| if (position->getColumnNumber() < 0) |
| return Response::ServerError("Position missing 'column' or 'column' < 0."); |
| return Response::Success(); |
| } |
| |
| Response isValidRangeOfPositions(std::vector<std::pair<int, int>>& positions) { |
| for (size_t i = 1; i < positions.size(); ++i) { |
| if (positions[i - 1].first < positions[i].first) continue; |
| if (positions[i - 1].first == positions[i].first && |
| positions[i - 1].second < positions[i].second) |
| continue; |
| return Response::ServerError( |
| "Input positions array is not sorted or contains duplicate values."); |
| } |
| return Response::Success(); |
| } |
| |
| bool hitBreakReasonEncodedAsOther(v8::debug::BreakReasons breakReasons) { |
| // The listed break reasons are not explicitly encoded in CDP when |
| // reporting the break. They are summarized as 'other'. |
| v8::debug::BreakReasons otherBreakReasons( |
| {v8::debug::BreakReason::kDebuggerStatement, |
| v8::debug::BreakReason::kScheduled, |
| v8::debug::BreakReason::kAlreadyPaused}); |
| return breakReasons.contains_any(otherBreakReasons); |
| } |
| } // namespace |
| |
| V8DebuggerAgentImpl::V8DebuggerAgentImpl( |
| V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel, |
| protocol::DictionaryValue* state) |
| : m_inspector(session->inspector()), |
| m_debugger(m_inspector->debugger()), |
| m_session(session), |
| m_enableState(kDisabled), |
| m_state(state), |
| m_frontend(frontendChannel), |
| m_isolate(m_inspector->isolate()) {} |
| |
| V8DebuggerAgentImpl::~V8DebuggerAgentImpl() = default; |
| |
| void V8DebuggerAgentImpl::enableImpl() { |
| m_enableState = kEnabled; |
| m_state->setBoolean(DebuggerAgentState::debuggerEnabled, true); |
| m_debugger->enable(); |
| |
| std::vector<std::unique_ptr<V8DebuggerScript>> compiledScripts = |
| m_debugger->getCompiledScripts(m_session->contextGroupId(), this); |
| for (auto& script : compiledScripts) { |
| didParseSource(std::move(script), true); |
| } |
| |
| m_breakpointsActive = m_state->booleanProperty( |
| DebuggerAgentState::breakpointsActiveWhenEnabled, true); |
| if (m_breakpointsActive) { |
| m_debugger->setBreakpointsActive(true); |
| } |
| if (isPaused()) { |
| didPause(0, v8::Local<v8::Value>(), std::vector<v8::debug::BreakpointId>(), |
| v8::debug::kException, false, |
| v8::debug::BreakReasons({v8::debug::BreakReason::kAlreadyPaused})); |
| } |
| } |
| |
| Response V8DebuggerAgentImpl::enable(Maybe<double> maxScriptsCacheSize, |
| String16* outDebuggerId) { |
| if (m_enableState == kStopping) |
| return Response::ServerError("Debugger is stopping"); |
| m_maxScriptCacheSize = v8::base::saturated_cast<size_t>( |
| maxScriptsCacheSize.value_or(std::numeric_limits<double>::max())); |
| m_state->setDouble(DebuggerAgentState::maxScriptCacheSize, |
| static_cast<double>(m_maxScriptCacheSize)); |
| *outDebuggerId = |
| m_debugger->debuggerIdFor(m_session->contextGroupId()).toString(); |
| if (enabled()) return Response::Success(); |
| |
| if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId())) |
| return Response::ServerError("Script execution is prohibited"); |
| |
| enableImpl(); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::disable() { |
| if (!enabled()) return Response::Success(); |
| |
| m_state->remove(DebuggerAgentState::breakpointsByRegex); |
| m_state->remove(DebuggerAgentState::breakpointsByUrl); |
| m_state->remove(DebuggerAgentState::breakpointsByScriptHash); |
| m_state->remove(DebuggerAgentState::breakpointHints); |
| m_state->remove(DebuggerAgentState::instrumentationBreakpoints); |
| |
| m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState, |
| v8::debug::NoBreakOnException); |
| m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, 0); |
| |
| if (m_breakpointsActive) { |
| m_debugger->setBreakpointsActive(false); |
| m_breakpointsActive = false; |
| } |
| m_blackboxedPositions.clear(); |
| m_blackboxPattern.reset(); |
| resetBlackboxedStateCache(); |
| m_skipList.clear(); |
| m_scripts.clear(); |
| m_cachedScripts.clear(); |
| m_cachedScriptSize = 0; |
| m_maxScriptCacheSize = 0; |
| m_state->setDouble(DebuggerAgentState::maxScriptCacheSize, 0); |
| for (const auto& it : m_debuggerBreakpointIdToBreakpointId) { |
| m_debugger->removeBreakpoint(it.first); |
| } |
| m_breakpointIdToDebuggerBreakpointIds.clear(); |
| m_debuggerBreakpointIdToBreakpointId.clear(); |
| m_wasmDisassemblies.clear(); |
| m_debugger->setAsyncCallStackDepth(this, 0); |
| clearBreakDetails(); |
| m_skipAllPauses = false; |
| m_state->setBoolean(DebuggerAgentState::skipAllPauses, false); |
| m_state->remove(DebuggerAgentState::blackboxPattern); |
| m_enableState = kDisabled; |
| m_instrumentationFinished = true; |
| m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false); |
| m_debugger->disable(); |
| return Response::Success(); |
| } |
| |
| void V8DebuggerAgentImpl::restore() { |
| DCHECK(m_enableState == kDisabled); |
| if (!m_state->booleanProperty(DebuggerAgentState::debuggerEnabled, false)) |
| return; |
| if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId())) |
| return; |
| |
| enableImpl(); |
| |
| double maxScriptCacheSize = 0; |
| m_state->getDouble(DebuggerAgentState::maxScriptCacheSize, |
| &maxScriptCacheSize); |
| m_maxScriptCacheSize = v8::base::saturated_cast<size_t>(maxScriptCacheSize); |
| |
| int pauseState = v8::debug::NoBreakOnException; |
| m_state->getInteger(DebuggerAgentState::pauseOnExceptionsState, &pauseState); |
| setPauseOnExceptionsImpl(pauseState); |
| |
| m_skipAllPauses = |
| m_state->booleanProperty(DebuggerAgentState::skipAllPauses, false); |
| |
| int asyncCallStackDepth = 0; |
| m_state->getInteger(DebuggerAgentState::asyncCallStackDepth, |
| &asyncCallStackDepth); |
| m_debugger->setAsyncCallStackDepth(this, asyncCallStackDepth); |
| |
| String16 blackboxPattern; |
| if (m_state->getString(DebuggerAgentState::blackboxPattern, |
| &blackboxPattern)) { |
| setBlackboxPattern(blackboxPattern); |
| } |
| } |
| |
| Response V8DebuggerAgentImpl::setBreakpointsActive(bool active) { |
| m_state->setBoolean(DebuggerAgentState::breakpointsActiveWhenEnabled, active); |
| if (!enabled()) return Response::Success(); |
| if (m_breakpointsActive == active) return Response::Success(); |
| m_breakpointsActive = active; |
| m_debugger->setBreakpointsActive(active); |
| if (!active && !m_breakReason.empty()) { |
| clearBreakDetails(); |
| m_debugger->setPauseOnNextCall(false, m_session->contextGroupId()); |
| } |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setSkipAllPauses(bool skip) { |
| m_state->setBoolean(DebuggerAgentState::skipAllPauses, skip); |
| m_skipAllPauses = skip; |
| return Response::Success(); |
| } |
| |
| namespace { |
| |
| class Matcher { |
| public: |
| Matcher(V8InspectorImpl* inspector, BreakpointType type, |
| const String16& selector) |
| : type_(type), selector_(selector) { |
| if (type == BreakpointType::kByUrlRegex) { |
| regex_ = std::make_unique<V8Regex>(inspector, selector, true); |
| } |
| } |
| |
| bool matches(const V8DebuggerScript& script) { |
| switch (type_) { |
| case BreakpointType::kByUrl: |
| return script.sourceURL() == selector_; |
| case BreakpointType::kByScriptHash: |
| return script.hash() == selector_; |
| case BreakpointType::kByUrlRegex: { |
| return regex_->match(script.sourceURL()) != -1; |
| } |
| case BreakpointType::kByScriptId: { |
| return script.scriptId() == selector_; |
| } |
| default: |
| return false; |
| } |
| } |
| |
| private: |
| std::unique_ptr<V8Regex> regex_; |
| BreakpointType type_; |
| const String16& selector_; |
| }; |
| |
| } // namespace |
| |
| Response V8DebuggerAgentImpl::setBreakpointByUrl( |
| int lineNumber, Maybe<String16> optionalURL, |
| Maybe<String16> optionalURLRegex, Maybe<String16> optionalScriptHash, |
| Maybe<int> optionalColumnNumber, Maybe<String16> optionalCondition, |
| String16* outBreakpointId, |
| std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| |
| *locations = std::make_unique<Array<protocol::Debugger::Location>>(); |
| |
| int specified = (optionalURL.has_value() ? 1 : 0) + |
| (optionalURLRegex.has_value() ? 1 : 0) + |
| (optionalScriptHash.has_value() ? 1 : 0); |
| if (specified != 1) { |
| return Response::ServerError( |
| "Either url or urlRegex or scriptHash must be specified."); |
| } |
| int columnNumber = 0; |
| if (optionalColumnNumber.has_value()) { |
| columnNumber = optionalColumnNumber.value(); |
| if (columnNumber < 0) |
| return Response::ServerError("Incorrect column number"); |
| } |
| |
| BreakpointType type = BreakpointType::kByUrl; |
| String16 selector; |
| if (optionalURLRegex.has_value()) { |
| selector = optionalURLRegex.value(); |
| type = BreakpointType::kByUrlRegex; |
| } else if (optionalURL.has_value()) { |
| selector = optionalURL.value(); |
| type = BreakpointType::kByUrl; |
| } else if (optionalScriptHash.has_value()) { |
| selector = optionalScriptHash.value(); |
| type = BreakpointType::kByScriptHash; |
| } |
| |
| // Note: This constructor can call into JavaScript. |
| Matcher matcher(m_inspector, type, selector); |
| |
| String16 condition = optionalCondition.value_or(String16()); |
| String16 breakpointId = |
| generateBreakpointId(type, selector, lineNumber, columnNumber); |
| protocol::DictionaryValue* breakpoints; |
| switch (type) { |
| case BreakpointType::kByUrlRegex: |
| breakpoints = |
| getOrCreateObject(m_state, DebuggerAgentState::breakpointsByRegex); |
| break; |
| case BreakpointType::kByUrl: |
| breakpoints = getOrCreateObject( |
| getOrCreateObject(m_state, DebuggerAgentState::breakpointsByUrl), |
| selector); |
| break; |
| case BreakpointType::kByScriptHash: |
| breakpoints = getOrCreateObject( |
| getOrCreateObject(m_state, |
| DebuggerAgentState::breakpointsByScriptHash), |
| selector); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| if (breakpoints->get(breakpointId)) { |
| return Response::ServerError( |
| "Breakpoint at specified location already exists."); |
| } |
| |
| std::unique_ptr<protocol::DictionaryValue> hint; |
| for (const auto& script : m_scripts) { |
| if (!matcher.matches(*script.second)) continue; |
| // Make sure the session was not disabled by some re-entrant call |
| // in the script matcher. |
| DCHECK(enabled()); |
| int adjustedLineNumber = lineNumber; |
| int adjustedColumnNumber = columnNumber; |
| if (hint) { |
| adjustBreakpointLocation(*script.second, hint.get(), &adjustedLineNumber, |
| &adjustedColumnNumber); |
| } |
| std::unique_ptr<protocol::Debugger::Location> location = |
| setBreakpointImpl(breakpointId, script.first, condition, |
| adjustedLineNumber, adjustedColumnNumber); |
| if (location && type != BreakpointType::kByUrlRegex) { |
| hint = breakpointHint(*script.second, lineNumber, columnNumber, |
| location->getLineNumber(), |
| location->getColumnNumber(adjustedColumnNumber)); |
| } |
| if (location) (*locations)->emplace_back(std::move(location)); |
| } |
| breakpoints->setString(breakpointId, condition); |
| if (hint) { |
| protocol::DictionaryValue* breakpointHints = |
| getOrCreateObject(m_state, DebuggerAgentState::breakpointHints); |
| breakpointHints->setObject(breakpointId, std::move(hint)); |
| } |
| *outBreakpointId = breakpointId; |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setBreakpoint( |
| std::unique_ptr<protocol::Debugger::Location> location, |
| Maybe<String16> optionalCondition, String16* outBreakpointId, |
| std::unique_ptr<protocol::Debugger::Location>* actualLocation) { |
| String16 breakpointId = generateBreakpointId( |
| BreakpointType::kByScriptId, location->getScriptId(), |
| location->getLineNumber(), location->getColumnNumber(0)); |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| |
| if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) != |
| m_breakpointIdToDebuggerBreakpointIds.end()) { |
| return Response::ServerError( |
| "Breakpoint at specified location already exists."); |
| } |
| *actualLocation = setBreakpointImpl(breakpointId, location->getScriptId(), |
| optionalCondition.value_or(String16()), |
| location->getLineNumber(), |
| location->getColumnNumber(0)); |
| if (!*actualLocation) |
| return Response::ServerError("Could not resolve breakpoint"); |
| *outBreakpointId = breakpointId; |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setBreakpointOnFunctionCall( |
| const String16& functionObjectId, Maybe<String16> optionalCondition, |
| String16* outBreakpointId) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| |
| InjectedScript::ObjectScope scope(m_session, functionObjectId); |
| Response response = scope.initialize(); |
| if (!response.IsSuccess()) return response; |
| if (!scope.object()->IsFunction()) { |
| return Response::ServerError("Could not find function with given id"); |
| } |
| v8::Local<v8::Function> function = |
| v8::Local<v8::Function>::Cast(scope.object()); |
| String16 breakpointId = |
| generateBreakpointId(BreakpointType::kBreakpointAtEntry, function); |
| if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) != |
| m_breakpointIdToDebuggerBreakpointIds.end()) { |
| return Response::ServerError( |
| "Breakpoint at specified location already exists."); |
| } |
| v8::Local<v8::String> condition = |
| toV8String(m_isolate, optionalCondition.value_or(String16())); |
| setBreakpointImpl(breakpointId, function, condition); |
| *outBreakpointId = breakpointId; |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setInstrumentationBreakpoint( |
| const String16& instrumentation, String16* outBreakpointId) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| String16 breakpointId = generateInstrumentationBreakpointId(instrumentation); |
| protocol::DictionaryValue* breakpoints = getOrCreateObject( |
| m_state, DebuggerAgentState::instrumentationBreakpoints); |
| if (breakpoints->get(breakpointId)) { |
| return Response::ServerError( |
| "Instrumentation breakpoint is already enabled."); |
| } |
| breakpoints->setBoolean(breakpointId, true); |
| *outBreakpointId = breakpointId; |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::removeBreakpoint(const String16& breakpointId) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| BreakpointType type; |
| String16 selector; |
| if (!parseBreakpointId(breakpointId, &type, &selector)) { |
| return Response::Success(); |
| } |
| Matcher matcher(m_inspector, type, selector); |
| protocol::DictionaryValue* breakpoints = nullptr; |
| switch (type) { |
| case BreakpointType::kByUrl: { |
| protocol::DictionaryValue* breakpointsByUrl = |
| m_state->getObject(DebuggerAgentState::breakpointsByUrl); |
| if (breakpointsByUrl) { |
| breakpoints = breakpointsByUrl->getObject(selector); |
| } |
| } break; |
| case BreakpointType::kByScriptHash: { |
| protocol::DictionaryValue* breakpointsByScriptHash = |
| m_state->getObject(DebuggerAgentState::breakpointsByScriptHash); |
| if (breakpointsByScriptHash) { |
| breakpoints = breakpointsByScriptHash->getObject(selector); |
| } |
| } break; |
| case BreakpointType::kByUrlRegex: |
| breakpoints = m_state->getObject(DebuggerAgentState::breakpointsByRegex); |
| break; |
| case BreakpointType::kInstrumentationBreakpoint: |
| breakpoints = |
| m_state->getObject(DebuggerAgentState::instrumentationBreakpoints); |
| break; |
| default: |
| break; |
| } |
| if (breakpoints) breakpoints->remove(breakpointId); |
| protocol::DictionaryValue* breakpointHints = |
| m_state->getObject(DebuggerAgentState::breakpointHints); |
| if (breakpointHints) breakpointHints->remove(breakpointId); |
| |
| // Get a list of scripts to remove breakpoints. |
| // TODO(duongn): we can do better here if from breakpoint id we can tell it is |
| // not Wasm breakpoint. |
| std::vector<V8DebuggerScript*> scripts; |
| for (const auto& scriptIter : m_scripts) { |
| const bool scriptSelectorMatch = matcher.matches(*scriptIter.second); |
| // Make sure the session was not disabled by some re-entrant call |
| // in the script matcher. |
| DCHECK(enabled()); |
| const bool isInstrumentation = |
| type == BreakpointType::kInstrumentationBreakpoint; |
| if (!scriptSelectorMatch && !isInstrumentation) continue; |
| V8DebuggerScript* script = scriptIter.second.get(); |
| if (script->getLanguage() == V8DebuggerScript::Language::WebAssembly) { |
| scripts.push_back(script); |
| } |
| } |
| removeBreakpointImpl(breakpointId, scripts); |
| |
| return Response::Success(); |
| } |
| |
| void V8DebuggerAgentImpl::removeBreakpointImpl( |
| const String16& breakpointId, |
| const std::vector<V8DebuggerScript*>& scripts) { |
| DCHECK(enabled()); |
| BreakpointIdToDebuggerBreakpointIdsMap::iterator |
| debuggerBreakpointIdsIterator = |
| m_breakpointIdToDebuggerBreakpointIds.find(breakpointId); |
| if (debuggerBreakpointIdsIterator == |
| m_breakpointIdToDebuggerBreakpointIds.end()) { |
| return; |
| } |
| for (const auto& id : debuggerBreakpointIdsIterator->second) { |
| #if V8_ENABLE_WEBASSEMBLY |
| for (auto& script : scripts) { |
| script->removeWasmBreakpoint(id); |
| } |
| #endif // V8_ENABLE_WEBASSEMBLY |
| m_debugger->removeBreakpoint(id); |
| m_debuggerBreakpointIdToBreakpointId.erase(id); |
| } |
| m_breakpointIdToDebuggerBreakpointIds.erase(breakpointId); |
| } |
| |
| Response V8DebuggerAgentImpl::getPossibleBreakpoints( |
| std::unique_ptr<protocol::Debugger::Location> start, |
| Maybe<protocol::Debugger::Location> end, Maybe<bool> restrictToFunction, |
| std::unique_ptr<protocol::Array<protocol::Debugger::BreakLocation>>* |
| locations) { |
| String16 scriptId = start->getScriptId(); |
| |
| if (start->getLineNumber() < 0 || start->getColumnNumber(0) < 0) |
| return Response::ServerError( |
| "start.lineNumber and start.columnNumber should be >= 0"); |
| |
| v8::debug::Location v8Start(start->getLineNumber(), |
| start->getColumnNumber(0)); |
| v8::debug::Location v8End; |
| if (end.has_value()) { |
| if (end->getScriptId() != scriptId) |
| return Response::ServerError( |
| "Locations should contain the same scriptId"); |
| int line = end->getLineNumber(); |
| int column = end->getColumnNumber(0); |
| if (line < 0 || column < 0) |
| return Response::ServerError( |
| "end.lineNumber and end.columnNumber should be >= 0"); |
| v8End = v8::debug::Location(line, column); |
| } |
| auto it = m_scripts.find(scriptId); |
| if (it == m_scripts.end()) return Response::ServerError("Script not found"); |
| std::vector<v8::debug::BreakLocation> v8Locations; |
| { |
| v8::HandleScope handleScope(m_isolate); |
| int contextId = it->second->executionContextId(); |
| InspectedContext* inspected = m_inspector->getContext(contextId); |
| if (!inspected) { |
| return Response::ServerError("Cannot retrive script context"); |
| } |
| v8::Context::Scope contextScope(inspected->context()); |
| v8::MicrotasksScope microtasks(inspected->context(), |
| v8::MicrotasksScope::kDoNotRunMicrotasks); |
| v8::TryCatch tryCatch(m_isolate); |
| it->second->getPossibleBreakpoints( |
| v8Start, v8End, restrictToFunction.value_or(false), &v8Locations); |
| } |
| |
| *locations = |
| std::make_unique<protocol::Array<protocol::Debugger::BreakLocation>>(); |
| |
| // TODO(1106269): Return an error instead of capping the number of |
| // breakpoints. |
| const size_t numBreakpointsToSend = |
| std::min(v8Locations.size(), kMaxNumBreakpoints); |
| for (size_t i = 0; i < numBreakpointsToSend; ++i) { |
| std::unique_ptr<protocol::Debugger::BreakLocation> breakLocation = |
| protocol::Debugger::BreakLocation::create() |
| .setScriptId(scriptId) |
| .setLineNumber(v8Locations[i].GetLineNumber()) |
| .setColumnNumber(v8Locations[i].GetColumnNumber()) |
| .build(); |
| if (v8Locations[i].type() != v8::debug::kCommonBreakLocation) { |
| breakLocation->setType(breakLocationType(v8Locations[i].type())); |
| } |
| (*locations)->emplace_back(std::move(breakLocation)); |
| } |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::continueToLocation( |
| std::unique_ptr<protocol::Debugger::Location> location, |
| Maybe<String16> targetCallFrames) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| ScriptsMap::iterator it = m_scripts.find(location->getScriptId()); |
| if (it == m_scripts.end()) { |
| return Response::ServerError("Cannot continue to specified location"); |
| } |
| V8DebuggerScript* script = it->second.get(); |
| int contextId = script->executionContextId(); |
| InspectedContext* inspected = m_inspector->getContext(contextId); |
| if (!inspected) |
| return Response::ServerError("Cannot continue to specified location"); |
| v8::HandleScope handleScope(m_isolate); |
| v8::Context::Scope contextScope(inspected->context()); |
| return m_debugger->continueToLocation( |
| m_session->contextGroupId(), script, std::move(location), |
| targetCallFrames.value_or( |
| protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any)); |
| } |
| |
| Response V8DebuggerAgentImpl::getStackTrace( |
| std::unique_ptr<protocol::Runtime::StackTraceId> inStackTraceId, |
| std::unique_ptr<protocol::Runtime::StackTrace>* outStackTrace) { |
| bool isOk = false; |
| int64_t id = inStackTraceId->getId().toInteger64(&isOk); |
| if (!isOk) return Response::ServerError("Invalid stack trace id"); |
| |
| internal::V8DebuggerId debuggerId; |
| if (inStackTraceId->hasDebuggerId()) { |
| debuggerId = |
| internal::V8DebuggerId(inStackTraceId->getDebuggerId(String16())); |
| } else { |
| debuggerId = m_debugger->debuggerIdFor(m_session->contextGroupId()); |
| } |
| if (!debuggerId.isValid()) |
| return Response::ServerError("Invalid stack trace id"); |
| |
| V8StackTraceId v8StackTraceId(id, debuggerId.pair()); |
| if (v8StackTraceId.IsInvalid()) |
| return Response::ServerError("Invalid stack trace id"); |
| auto stack = |
| m_debugger->stackTraceFor(m_session->contextGroupId(), v8StackTraceId); |
| if (!stack) { |
| return Response::ServerError("Stack trace with given id is not found"); |
| } |
| *outStackTrace = stack->buildInspectorObject( |
| m_debugger, m_debugger->maxAsyncCallChainDepth()); |
| return Response::Success(); |
| } |
| |
| bool V8DebuggerAgentImpl::isFunctionBlackboxed(const String16& scriptId, |
| const v8::debug::Location& start, |
| const v8::debug::Location& end) { |
| ScriptsMap::iterator it = m_scripts.find(scriptId); |
| if (it == m_scripts.end()) { |
| // Unknown scripts are blackboxed. |
| return true; |
| } |
| if (m_blackboxPattern) { |
| const String16& scriptSourceURL = it->second->sourceURL(); |
| if (!scriptSourceURL.isEmpty() && |
| m_blackboxPattern->match(scriptSourceURL) != -1) |
| return true; |
| } |
| auto itBlackboxedPositions = m_blackboxedPositions.find(scriptId); |
| if (itBlackboxedPositions == m_blackboxedPositions.end()) return false; |
| |
| const std::vector<std::pair<int, int>>& ranges = |
| itBlackboxedPositions->second; |
| auto itStartRange = std::lower_bound( |
| ranges.begin(), ranges.end(), |
| std::make_pair(start.GetLineNumber(), start.GetColumnNumber()), |
| positionComparator); |
| auto itEndRange = std::lower_bound( |
| itStartRange, ranges.end(), |
| std::make_pair(end.GetLineNumber(), end.GetColumnNumber()), |
| positionComparator); |
| // Ranges array contains positions in script where blackbox state is changed. |
| // [(0,0) ... ranges[0]) isn't blackboxed, [ranges[0] ... ranges[1]) is |
| // blackboxed... |
| return itStartRange == itEndRange && |
| std::distance(ranges.begin(), itStartRange) % 2; |
| } |
| |
| bool V8DebuggerAgentImpl::shouldBeSkipped(const String16& scriptId, int line, |
| int column) { |
| if (m_skipList.empty()) return false; |
| |
| auto it = m_skipList.find(scriptId); |
| if (it == m_skipList.end()) return false; |
| |
| const std::vector<std::pair<int, int>>& ranges = it->second; |
| DCHECK(!ranges.empty()); |
| const std::pair<int, int> location = std::make_pair(line, column); |
| auto itLowerBound = std::lower_bound(ranges.begin(), ranges.end(), location, |
| positionComparator); |
| |
| bool shouldSkip = false; |
| if (itLowerBound != ranges.end()) { |
| // Skip lists are defined as pairs of locations that specify the |
| // start and the end of ranges to skip: [ranges[0], ranges[1], ..], where |
| // locations in [ranges[0], ranges[1]) should be skipped, i.e. |
| // [(lineStart, columnStart), (lineEnd, columnEnd)). |
| const bool isSameAsLowerBound = location == *itLowerBound; |
| const bool isUnevenIndex = (itLowerBound - ranges.begin()) % 2; |
| shouldSkip = isSameAsLowerBound ^ isUnevenIndex; |
| } |
| |
| return shouldSkip; |
| } |
| |
| bool V8DebuggerAgentImpl::acceptsPause(bool isOOMBreak) const { |
| return enabled() && (isOOMBreak || !m_skipAllPauses); |
| } |
| |
| std::unique_ptr<protocol::Debugger::Location> |
| V8DebuggerAgentImpl::setBreakpointImpl(const String16& breakpointId, |
| const String16& scriptId, |
| const String16& condition, |
| int lineNumber, int columnNumber) { |
| v8::HandleScope handles(m_isolate); |
| DCHECK(enabled()); |
| |
| ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId); |
| if (scriptIterator == m_scripts.end()) return nullptr; |
| V8DebuggerScript* script = scriptIterator->second.get(); |
| |
| v8::debug::BreakpointId debuggerBreakpointId; |
| v8::debug::Location location(lineNumber, columnNumber); |
| int contextId = script->executionContextId(); |
| InspectedContext* inspected = m_inspector->getContext(contextId); |
| if (!inspected) return nullptr; |
| |
| { |
| v8::Context::Scope contextScope(inspected->context()); |
| if (!script->setBreakpoint(condition, &location, &debuggerBreakpointId)) { |
| return nullptr; |
| } |
| } |
| |
| m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId; |
| m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back( |
| debuggerBreakpointId); |
| |
| return protocol::Debugger::Location::create() |
| .setScriptId(scriptId) |
| .setLineNumber(location.GetLineNumber()) |
| .setColumnNumber(location.GetColumnNumber()) |
| .build(); |
| } |
| |
| void V8DebuggerAgentImpl::setBreakpointImpl(const String16& breakpointId, |
| v8::Local<v8::Function> function, |
| v8::Local<v8::String> condition) { |
| v8::debug::BreakpointId debuggerBreakpointId; |
| if (!v8::debug::SetFunctionBreakpoint(function, condition, |
| &debuggerBreakpointId)) { |
| return; |
| } |
| m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId; |
| m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back( |
| debuggerBreakpointId); |
| } |
| |
| Response V8DebuggerAgentImpl::searchInContent( |
| const String16& scriptId, const String16& query, |
| Maybe<bool> optionalCaseSensitive, Maybe<bool> optionalIsRegex, |
| std::unique_ptr<Array<protocol::Debugger::SearchMatch>>* results) { |
| v8::HandleScope handles(m_isolate); |
| ScriptsMap::iterator it = m_scripts.find(scriptId); |
| if (it == m_scripts.end()) |
| return Response::ServerError("No script for id: " + scriptId.utf8()); |
| |
| *results = std::make_unique<protocol::Array<protocol::Debugger::SearchMatch>>( |
| searchInTextByLinesImpl(m_session, it->second->source(0), query, |
| optionalCaseSensitive.value_or(false), |
| optionalIsRegex.value_or(false))); |
| return Response::Success(); |
| } |
| |
| namespace { |
| const char* buildStatus(v8::debug::LiveEditResult::Status status) { |
| switch (status) { |
| case v8::debug::LiveEditResult::OK: |
| return protocol::Debugger::SetScriptSource::StatusEnum::Ok; |
| case v8::debug::LiveEditResult::COMPILE_ERROR: |
| return protocol::Debugger::SetScriptSource::StatusEnum::CompileError; |
| case v8::debug::LiveEditResult::BLOCKED_BY_ACTIVE_FUNCTION: |
| return protocol::Debugger::SetScriptSource::StatusEnum:: |
| BlockedByActiveFunction; |
| case v8::debug::LiveEditResult::BLOCKED_BY_RUNNING_GENERATOR: |
| return protocol::Debugger::SetScriptSource::StatusEnum:: |
| BlockedByActiveGenerator; |
| case v8::debug::LiveEditResult::BLOCKED_BY_TOP_LEVEL_ES_MODULE_CHANGE: |
| return protocol::Debugger::SetScriptSource::StatusEnum:: |
| BlockedByTopLevelEsModuleChange; |
| } |
| } |
| } // namespace |
| |
| Response V8DebuggerAgentImpl::setScriptSource( |
| const String16& scriptId, const String16& newContent, Maybe<bool> dryRun, |
| Maybe<bool> allowTopFrameEditing, |
| Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames, |
| Maybe<bool>* stackChanged, |
| Maybe<protocol::Runtime::StackTrace>* asyncStackTrace, |
| Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId, String16* status, |
| Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| |
| ScriptsMap::iterator it = m_scripts.find(scriptId); |
| if (it == m_scripts.end()) { |
| return Response::ServerError("No script with given id found"); |
| } |
| int contextId = it->second->executionContextId(); |
| InspectedContext* inspected = m_inspector->getContext(contextId); |
| if (!inspected) { |
| return Response::InternalError(); |
| } |
| v8::HandleScope handleScope(m_isolate); |
| v8::Local<v8::Context> context = inspected->context(); |
| v8::Context::Scope contextScope(context); |
| const bool allowTopFrameLiveEditing = allowTopFrameEditing.value_or(false); |
| |
| v8::debug::LiveEditResult result; |
| it->second->setSource(newContent, dryRun.value_or(false), |
| allowTopFrameLiveEditing, &result); |
| *status = buildStatus(result.status); |
| if (result.status == v8::debug::LiveEditResult::COMPILE_ERROR) { |
| *optOutCompileError = |
| protocol::Runtime::ExceptionDetails::create() |
| .setExceptionId(m_inspector->nextExceptionId()) |
| .setText(toProtocolString(m_isolate, result.message)) |
| .setLineNumber(result.line_number != -1 ? result.line_number - 1 |
| : 0) |
| .setColumnNumber(result.column_number != -1 ? result.column_number |
| : 0) |
| .build(); |
| return Response::Success(); |
| } |
| |
| if (result.restart_top_frame_required) { |
| CHECK(allowTopFrameLiveEditing); |
| // Nothing could have happened to the JS stack since the live edit so |
| // restarting the top frame is guaranteed to be successful. |
| CHECK(m_debugger->restartFrame(m_session->contextGroupId(), |
| /* callFrameOrdinal */ 0)); |
| m_session->releaseObjectGroup(kBacktraceObjectGroup); |
| } |
| |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::restartFrame( |
| const String16& callFrameId, Maybe<String16> mode, |
| std::unique_ptr<Array<CallFrame>>* newCallFrames, |
| Maybe<protocol::Runtime::StackTrace>* asyncStackTrace, |
| Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId) { |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| if (!mode.has_value()) { |
| return Response::ServerError( |
| "Restarting frame without 'mode' not supported"); |
| } |
| if (mode.value() != protocol::Debugger::RestartFrame::ModeEnum::StepInto) { |
| return Response::InvalidParams("'StepInto' is the only valid mode"); |
| } |
| |
| InjectedScript::CallFrameScope scope(m_session, callFrameId); |
| Response response = scope.initialize(); |
| if (!response.IsSuccess()) return response; |
| int callFrameOrdinal = static_cast<int>(scope.frameOrdinal()); |
| |
| if (!m_debugger->restartFrame(m_session->contextGroupId(), |
| callFrameOrdinal)) { |
| return Response::ServerError("Restarting frame failed"); |
| } |
| m_session->releaseObjectGroup(kBacktraceObjectGroup); |
| *newCallFrames = std::make_unique<Array<CallFrame>>(); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::getScriptSource( |
| const String16& scriptId, String16* scriptSource, |
| Maybe<protocol::Binary>* bytecode) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| ScriptsMap::iterator it = m_scripts.find(scriptId); |
| if (it == m_scripts.end()) { |
| auto cachedScriptIt = |
| std::find_if(m_cachedScripts.begin(), m_cachedScripts.end(), |
| [&scriptId](const CachedScript& cachedScript) { |
| return cachedScript.scriptId == scriptId; |
| }); |
| if (cachedScriptIt != m_cachedScripts.end()) { |
| *scriptSource = cachedScriptIt->source; |
| *bytecode = protocol::Binary::fromSpan(cachedScriptIt->bytecode.data(), |
| cachedScriptIt->bytecode.size()); |
| return Response::Success(); |
| } |
| return Response::ServerError("No script for id: " + scriptId.utf8()); |
| } |
| *scriptSource = it->second->source(0); |
| #if V8_ENABLE_WEBASSEMBLY |
| v8::MemorySpan<const uint8_t> span; |
| if (it->second->wasmBytecode().To(&span)) { |
| if (span.size() > kWasmBytecodeMaxLength) { |
| return Response::ServerError(kWasmBytecodeExceedsTransferLimit); |
| } |
| *bytecode = protocol::Binary::fromSpan(span.data(), span.size()); |
| } |
| #endif // V8_ENABLE_WEBASSEMBLY |
| return Response::Success(); |
| } |
| |
| struct DisassemblyChunk { |
| DisassemblyChunk() = default; |
| DisassemblyChunk(const DisassemblyChunk& other) = delete; |
| DisassemblyChunk& operator=(const DisassemblyChunk& other) = delete; |
| DisassemblyChunk(DisassemblyChunk&& other) V8_NOEXCEPT = default; |
| DisassemblyChunk& operator=(DisassemblyChunk&& other) V8_NOEXCEPT = default; |
| |
| std::vector<String16> lines; |
| std::vector<int> lineOffsets; |
| |
| void Reserve(size_t size) { |
| lines.reserve(size); |
| lineOffsets.reserve(size); |
| } |
| }; |
| |
| class DisassemblyCollectorImpl final : public v8::debug::DisassemblyCollector { |
| public: |
| DisassemblyCollectorImpl() = default; |
| |
| void ReserveLineCount(size_t count) override { |
| if (count == 0) return; |
| size_t num_chunks = (count + kLinesPerChunk - 1) / kLinesPerChunk; |
| chunks_.resize(num_chunks); |
| for (size_t i = 0; i < num_chunks - 1; i++) { |
| chunks_[i].Reserve(kLinesPerChunk); |
| } |
| size_t last = num_chunks - 1; |
| size_t last_size = count % kLinesPerChunk; |
| if (last_size == 0) last_size = kLinesPerChunk; |
| chunks_[last].Reserve(last_size); |
| } |
| |
| void AddLine(const char* src, size_t length, |
| uint32_t bytecode_offset) override { |
| chunks_[writing_chunk_index_].lines.emplace_back(src, length); |
| chunks_[writing_chunk_index_].lineOffsets.push_back( |
| static_cast<int>(bytecode_offset)); |
| if (chunks_[writing_chunk_index_].lines.size() == kLinesPerChunk) { |
| writing_chunk_index_++; |
| } |
| total_number_of_lines_++; |
| } |
| |
| size_t total_number_of_lines() { return total_number_of_lines_; } |
| |
| bool HasNextChunk() { return reading_chunk_index_ < chunks_.size(); } |
| DisassemblyChunk NextChunk() { |
| return std::move(chunks_[reading_chunk_index_++]); |
| } |
| |
| private: |
| // For a large Ritz module, the average is about 50 chars per line, |
| // so (with 2-byte String16 chars) this should give approximately 20 MB |
| // per chunk. |
| static constexpr size_t kLinesPerChunk = 200'000; |
| |
| size_t writing_chunk_index_ = 0; |
| size_t reading_chunk_index_ = 0; |
| size_t total_number_of_lines_ = 0; |
| std::vector<DisassemblyChunk> chunks_; |
| }; |
| |
| Response V8DebuggerAgentImpl::disassembleWasmModule( |
| const String16& in_scriptId, Maybe<String16>* out_streamId, |
| int* out_totalNumberOfLines, |
| std::unique_ptr<protocol::Array<int>>* out_functionBodyOffsets, |
| std::unique_ptr<protocol::Debugger::WasmDisassemblyChunk>* out_chunk) { |
| #if V8_ENABLE_WEBASSEMBLY |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| ScriptsMap::iterator it = m_scripts.find(in_scriptId); |
| std::unique_ptr<DisassemblyCollectorImpl> collector = |
| std::make_unique<DisassemblyCollectorImpl>(); |
| std::vector<int> functionBodyOffsets; |
| if (it != m_scripts.end()) { |
| V8DebuggerScript* script = it->second.get(); |
| if (script->getLanguage() != V8DebuggerScript::Language::WebAssembly) { |
| return Response::InvalidParams("Script with id " + in_scriptId.utf8() + |
| " is not WebAssembly"); |
| } |
| script->Disassemble(collector.get(), &functionBodyOffsets); |
| } else { |
| auto cachedScriptIt = |
| std::find_if(m_cachedScripts.begin(), m_cachedScripts.end(), |
| [&in_scriptId](const CachedScript& cachedScript) { |
| return cachedScript.scriptId == in_scriptId; |
| }); |
| if (cachedScriptIt == m_cachedScripts.end()) { |
| return Response::InvalidParams("No script for id: " + in_scriptId.utf8()); |
| } |
| v8::debug::Disassemble(v8::base::VectorOf(cachedScriptIt->bytecode), |
| collector.get(), &functionBodyOffsets); |
| } |
| *out_totalNumberOfLines = |
| static_cast<int>(collector->total_number_of_lines()); |
| *out_functionBodyOffsets = |
| std::make_unique<protocol::Array<int>>(std::move(functionBodyOffsets)); |
| // Even an empty module would disassemble to "(module)", never to zero lines. |
| DCHECK(collector->HasNextChunk()); |
| DisassemblyChunk chunk(collector->NextChunk()); |
| *out_chunk = protocol::Debugger::WasmDisassemblyChunk::create() |
| .setBytecodeOffsets(std::make_unique<protocol::Array<int>>( |
| std::move(chunk.lineOffsets))) |
| .setLines(std::make_unique<protocol::Array<String16>>( |
| std::move(chunk.lines))) |
| .build(); |
| if (collector->HasNextChunk()) { |
| String16 streamId = String16::fromInteger(m_nextWasmDisassemblyStreamId++); |
| *out_streamId = streamId; |
| m_wasmDisassemblies[streamId] = std::move(collector); |
| } |
| return Response::Success(); |
| #else |
| return Response::ServerError("WebAssembly is disabled"); |
| #endif // V8_ENABLE_WEBASSEMBLY |
| } |
| |
| Response V8DebuggerAgentImpl::nextWasmDisassemblyChunk( |
| const String16& in_streamId, |
| std::unique_ptr<protocol::Debugger::WasmDisassemblyChunk>* out_chunk) { |
| #if V8_ENABLE_WEBASSEMBLY |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| auto it = m_wasmDisassemblies.find(in_streamId); |
| if (it == m_wasmDisassemblies.end()) { |
| return Response::InvalidParams("No chunks available for stream " + |
| in_streamId.utf8()); |
| } |
| if (it->second->HasNextChunk()) { |
| DisassemblyChunk chunk(it->second->NextChunk()); |
| *out_chunk = protocol::Debugger::WasmDisassemblyChunk::create() |
| .setBytecodeOffsets(std::make_unique<protocol::Array<int>>( |
| std::move(chunk.lineOffsets))) |
| .setLines(std::make_unique<protocol::Array<String16>>( |
| std::move(chunk.lines))) |
| .build(); |
| } else { |
| *out_chunk = |
| protocol::Debugger::WasmDisassemblyChunk::create() |
| .setBytecodeOffsets(std::make_unique<protocol::Array<int>>()) |
| .setLines(std::make_unique<protocol::Array<String16>>()) |
| .build(); |
| m_wasmDisassemblies.erase(it); |
| } |
| return Response::Success(); |
| #else |
| return Response::ServerError("WebAssembly is disabled"); |
| #endif // V8_ENABLE_WEBASSEMBLY |
| } |
| |
| Response V8DebuggerAgentImpl::getWasmBytecode(const String16& scriptId, |
| protocol::Binary* bytecode) { |
| #if V8_ENABLE_WEBASSEMBLY |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| ScriptsMap::iterator it = m_scripts.find(scriptId); |
| if (it == m_scripts.end()) |
| return Response::ServerError("No script for id: " + scriptId.utf8()); |
| v8::MemorySpan<const uint8_t> span; |
| if (!it->second->wasmBytecode().To(&span)) |
| return Response::ServerError("Script with id " + scriptId.utf8() + |
| " is not WebAssembly"); |
| if (span.size() > kWasmBytecodeMaxLength) { |
| return Response::ServerError(kWasmBytecodeExceedsTransferLimit); |
| } |
| *bytecode = protocol::Binary::fromSpan(span.data(), span.size()); |
| return Response::Success(); |
| #else |
| return Response::ServerError("WebAssembly is disabled"); |
| #endif // V8_ENABLE_WEBASSEMBLY |
| } |
| |
| void V8DebuggerAgentImpl::pushBreakDetails( |
| const String16& breakReason, |
| std::unique_ptr<protocol::DictionaryValue> breakAuxData) { |
| m_breakReason.push_back(std::make_pair(breakReason, std::move(breakAuxData))); |
| } |
| |
| void V8DebuggerAgentImpl::popBreakDetails() { |
| if (m_breakReason.empty()) return; |
| m_breakReason.pop_back(); |
| } |
| |
| void V8DebuggerAgentImpl::clearBreakDetails() { |
| std::vector<BreakReason> emptyBreakReason; |
| m_breakReason.swap(emptyBreakReason); |
| } |
| |
| void V8DebuggerAgentImpl::schedulePauseOnNextStatement( |
| const String16& breakReason, |
| std::unique_ptr<protocol::DictionaryValue> data) { |
| if (isPaused() || !acceptsPause(false) || !m_breakpointsActive) return; |
| if (m_breakReason.empty()) { |
| m_debugger->setPauseOnNextCall(true, m_session->contextGroupId()); |
| } |
| pushBreakDetails(breakReason, std::move(data)); |
| } |
| |
| void V8DebuggerAgentImpl::cancelPauseOnNextStatement() { |
| if (isPaused() || !acceptsPause(false) || !m_breakpointsActive) return; |
| if (m_breakReason.size() == 1) { |
| m_debugger->setPauseOnNextCall(false, m_session->contextGroupId()); |
| } |
| popBreakDetails(); |
| } |
| |
| Response V8DebuggerAgentImpl::pause() { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| |
| if (m_debugger->isInInstrumentationPause()) { |
| // If we are inside an instrumentation pause, remember the pause request |
| // so that we can enter the requested pause once we are done |
| // with the instrumentation. |
| m_debugger->requestPauseAfterInstrumentation(); |
| } else if (isPaused()) { |
| // Ignore the pause request if we are already paused. |
| return Response::Success(); |
| } else if (m_debugger->canBreakProgram()) { |
| m_debugger->interruptAndBreak(m_session->contextGroupId()); |
| } else { |
| pushBreakDetails(protocol::Debugger::Paused::ReasonEnum::Other, nullptr); |
| m_debugger->setPauseOnNextCall(true, m_session->contextGroupId()); |
| } |
| |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::resume(Maybe<bool> terminateOnResume) { |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| m_session->releaseObjectGroup(kBacktraceObjectGroup); |
| |
| m_instrumentationFinished = true; |
| m_debugger->continueProgram(m_session->contextGroupId(), |
| terminateOnResume.value_or(false)); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::stepOver( |
| Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) { |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| |
| if (inSkipList.has_value()) { |
| const Response res = processSkipList(inSkipList.value()); |
| if (res.IsError()) return res; |
| } else { |
| m_skipList.clear(); |
| } |
| |
| m_session->releaseObjectGroup(kBacktraceObjectGroup); |
| m_debugger->stepOverStatement(m_session->contextGroupId()); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::stepInto( |
| Maybe<bool> inBreakOnAsyncCall, |
| Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) { |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| |
| if (inSkipList.has_value()) { |
| const Response res = processSkipList(inSkipList.value()); |
| if (res.IsError()) return res; |
| } else { |
| m_skipList.clear(); |
| } |
| |
| m_session->releaseObjectGroup(kBacktraceObjectGroup); |
| m_debugger->stepIntoStatement(m_session->contextGroupId(), |
| inBreakOnAsyncCall.value_or(false)); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::stepOut() { |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| m_session->releaseObjectGroup(kBacktraceObjectGroup); |
| m_debugger->stepOutOfFunction(m_session->contextGroupId()); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::pauseOnAsyncCall( |
| std::unique_ptr<protocol::Runtime::StackTraceId> inParentStackTraceId) { |
| // Deprecated, just return OK. |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setPauseOnExceptions( |
| const String16& stringPauseState) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| v8::debug::ExceptionBreakState pauseState; |
| if (stringPauseState == "none") { |
| pauseState = v8::debug::NoBreakOnException; |
| } else if (stringPauseState == "all") { |
| pauseState = v8::debug::BreakOnAnyException; |
| } else if (stringPauseState == "caught") { |
| pauseState = v8::debug::BreakOnCaughtException; |
| } else if (stringPauseState == "uncaught") { |
| pauseState = v8::debug::BreakOnUncaughtException; |
| } else { |
| return Response::ServerError("Unknown pause on exceptions mode: " + |
| stringPauseState.utf8()); |
| } |
| setPauseOnExceptionsImpl(pauseState); |
| return Response::Success(); |
| } |
| |
| void V8DebuggerAgentImpl::setPauseOnExceptionsImpl(int pauseState) { |
| // TODO(dgozman): this changes the global state and forces all context groups |
| // to pause. We should make this flag be per-context-group. |
| m_debugger->setPauseOnExceptionsState( |
| static_cast<v8::debug::ExceptionBreakState>(pauseState)); |
| m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState, pauseState); |
| } |
| |
| Response V8DebuggerAgentImpl::evaluateOnCallFrame( |
| const String16& callFrameId, const String16& expression, |
| Maybe<String16> objectGroup, Maybe<bool> includeCommandLineAPI, |
| Maybe<bool> silent, Maybe<bool> returnByValue, Maybe<bool> generatePreview, |
| Maybe<bool> throwOnSideEffect, Maybe<double> timeout, |
| std::unique_ptr<RemoteObject>* result, |
| Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) { |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| InjectedScript::CallFrameScope scope(m_session, callFrameId); |
| Response response = scope.initialize(); |
| if (!response.IsSuccess()) return response; |
| if (includeCommandLineAPI.value_or(false)) scope.installCommandLineAPI(); |
| if (silent.value_or(false)) scope.ignoreExceptionsAndMuteConsole(); |
| |
| int frameOrdinal = static_cast<int>(scope.frameOrdinal()); |
| auto it = v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal); |
| if (it->Done()) { |
| return Response::ServerError("Could not find call frame with given id"); |
| } |
| |
| v8::MaybeLocal<v8::Value> maybeResultValue; |
| { |
| V8InspectorImpl::EvaluateScope evaluateScope(scope); |
| if (timeout.has_value()) { |
| response = evaluateScope.setTimeout(timeout.value() / 1000.0); |
| if (!response.IsSuccess()) return response; |
| } |
| maybeResultValue = it->Evaluate(toV8String(m_isolate, expression), |
| throwOnSideEffect.value_or(false)); |
| } |
| // Re-initialize after running client's code, as it could have destroyed |
| // context or session. |
| response = scope.initialize(); |
| if (!response.IsSuccess()) return response; |
| WrapOptions wrapOptions = generatePreview.value_or(false) |
| ? WrapOptions({WrapMode::kPreview}) |
| : WrapOptions({WrapMode::kIdOnly}); |
| if (returnByValue.value_or(false)) |
| wrapOptions = WrapOptions({WrapMode::kJson}); |
| return scope.injectedScript()->wrapEvaluateResult( |
| maybeResultValue, scope.tryCatch(), objectGroup.value_or(""), wrapOptions, |
| throwOnSideEffect.value_or(false), result, exceptionDetails); |
| } |
| |
| Response V8DebuggerAgentImpl::setVariableValue( |
| int scopeNumber, const String16& variableName, |
| std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument, |
| const String16& callFrameId) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| InjectedScript::CallFrameScope scope(m_session, callFrameId); |
| Response response = scope.initialize(); |
| if (!response.IsSuccess()) return response; |
| v8::Local<v8::Value> newValue; |
| response = scope.injectedScript()->resolveCallArgument(newValueArgument.get(), |
| &newValue); |
| if (!response.IsSuccess()) return response; |
| |
| int frameOrdinal = static_cast<int>(scope.frameOrdinal()); |
| auto it = v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal); |
| if (it->Done()) { |
| return Response::ServerError("Could not find call frame with given id"); |
| } |
| auto scopeIterator = it->GetScopeIterator(); |
| while (!scopeIterator->Done() && scopeNumber > 0) { |
| --scopeNumber; |
| scopeIterator->Advance(); |
| } |
| if (scopeNumber != 0) { |
| return Response::ServerError("Could not find scope with given number"); |
| } |
| |
| if (!scopeIterator->SetVariableValue(toV8String(m_isolate, variableName), |
| newValue) || |
| scope.tryCatch().HasCaught()) { |
| return Response::InternalError(); |
| } |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setReturnValue( |
| std::unique_ptr<protocol::Runtime::CallArgument> protocolNewValue) { |
| if (!enabled()) return Response::ServerError(kDebuggerNotEnabled); |
| if (!isPaused()) return Response::ServerError(kDebuggerNotPaused); |
| v8::HandleScope handleScope(m_isolate); |
| auto iterator = v8::debug::StackTraceIterator::Create(m_isolate); |
| if (iterator->Done()) { |
| return Response::ServerError("Could not find top call frame"); |
| } |
| if (iterator->GetReturnValue().IsEmpty()) { |
| return Response::ServerError( |
| "Could not update return value at non-return position"); |
| } |
| InjectedScript::ContextScope scope(m_session, iterator->GetContextId()); |
| Response response = scope.initialize(); |
| if (!response.IsSuccess()) return response; |
| v8::Local<v8::Value> newValue; |
| response = scope.injectedScript()->resolveCallArgument(protocolNewValue.get(), |
| &newValue); |
| if (!response.IsSuccess()) return response; |
| v8::debug::SetReturnValue(m_isolate, newValue); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setAsyncCallStackDepth(int depth) { |
| if (!enabled() && !m_session->runtimeAgent()->enabled()) { |
| return Response::ServerError(kDebuggerNotEnabled); |
| } |
| m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, depth); |
| m_debugger->setAsyncCallStackDepth(this, depth); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setBlackboxPatterns( |
| std::unique_ptr<protocol::Array<String16>> patterns) { |
| if (patterns->empty()) { |
| m_blackboxPattern = nullptr; |
| resetBlackboxedStateCache(); |
| m_state->remove(DebuggerAgentState::blackboxPattern); |
| return Response::Success(); |
| } |
| |
| String16Builder patternBuilder; |
| patternBuilder.append('('); |
| for (size_t i = 0; i < patterns->size() - 1; ++i) { |
| patternBuilder.append((*patterns)[i]); |
| patternBuilder.append("|"); |
| } |
| patternBuilder.append(patterns->back()); |
| patternBuilder.append(')'); |
| String16 pattern = patternBuilder.toString(); |
| Response response = setBlackboxPattern(pattern); |
| if (!response.IsSuccess()) return response; |
| resetBlackboxedStateCache(); |
| m_state->setString(DebuggerAgentState::blackboxPattern, pattern); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::setBlackboxPattern(const String16& pattern) { |
| std::unique_ptr<V8Regex> regex(new V8Regex( |
| m_inspector, pattern, true /** caseSensitive */, false /** multiline */)); |
| if (!regex->isValid()) |
| return Response::ServerError("Pattern parser error: " + |
| regex->errorMessage().utf8()); |
| m_blackboxPattern = std::move(regex); |
| return Response::Success(); |
| } |
| |
| void V8DebuggerAgentImpl::resetBlackboxedStateCache() { |
| for (const auto& it : m_scripts) { |
| it.second->resetBlackboxedStateCache(); |
| } |
| } |
| |
| Response V8DebuggerAgentImpl::setBlackboxedRanges( |
| const String16& scriptId, |
| std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>> |
| inPositions) { |
| auto it = m_scripts.find(scriptId); |
| if (it == m_scripts.end()) |
| return Response::ServerError("No script with passed id."); |
| |
| if (inPositions->empty()) { |
| m_blackboxedPositions.erase(scriptId); |
| it->second->resetBlackboxedStateCache(); |
| return Response::Success(); |
| } |
| |
| std::vector<std::pair<int, int>> positions; |
| positions.reserve(inPositions->size()); |
| for (const std::unique_ptr<protocol::Debugger::ScriptPosition>& position : |
| *inPositions) { |
| Response res = isValidPosition(position.get()); |
| if (res.IsError()) return res; |
| |
| positions.push_back( |
| std::make_pair(position->getLineNumber(), position->getColumnNumber())); |
| } |
| Response res = isValidRangeOfPositions(positions); |
| if (res.IsError()) return res; |
| |
| m_blackboxedPositions[scriptId] = positions; |
| it->second->resetBlackboxedStateCache(); |
| return Response::Success(); |
| } |
| |
| Response V8DebuggerAgentImpl::currentCallFrames( |
| std::unique_ptr<Array<CallFrame>>* result) { |
| if (!isPaused()) { |
| *result = std::make_unique<Array<CallFrame>>(); |
| return Response::Success(); |
| } |
| v8::HandleScope handles(m_isolate); |
| *result = std::make_unique<Array<CallFrame>>(); |
| auto iterator = v8::debug::StackTraceIterator::Create(m_isolate); |
| int frameOrdinal = 0; |
| for (; !iterator->Done(); iterator->Advance(), frameOrdinal++) { |
| int contextId = iterator->GetContextId(); |
| InjectedScript* injectedScript = nullptr; |
| if (contextId) m_session->findInjectedScript(contextId, injectedScript); |
| String16 callFrameId = RemoteCallFrameId::serialize( |
| m_inspector->isolateId(), contextId, frameOrdinal); |
| |
| v8::debug::Location loc = iterator->GetSourceLocation(); |
| |
| std::unique_ptr<Array<Scope>> scopes; |
| auto scopeIterator = iterator->GetScopeIterator(); |
| Response res = |
| buildScopes(m_isolate, scopeIterator.get(), injectedScript, &scopes); |
| if (!res.IsSuccess()) return res; |
| |
| std::unique_ptr<RemoteObject> protocolReceiver; |
| if (injectedScript) { |
| v8::Local<v8::Value> receiver; |
| if (iterator->GetReceiver().ToLocal(&receiver)) { |
| res = injectedScript->wrapObject(receiver, kBacktraceObjectGroup, |
| WrapOptions({WrapMode::kIdOnly}), |
| &protocolReceiver); |
| if (!res.IsSuccess()) return res; |
| } |
| } |
| if (!protocolReceiver) { |
| protocolReceiver = RemoteObject::create() |
| .setType(RemoteObject::TypeEnum::Undefined) |
| .build(); |
| } |
| |
| v8::Local<v8::debug::Script> script = iterator->GetScript(); |
| DCHECK(!script.IsEmpty()); |
| std::unique_ptr<protocol::Debugger::Location> location = |
| protocol::Debugger::Location::create() |
| .setScriptId(String16::fromInteger(script->Id())) |
| .setLineNumber(loc.GetLineNumber()) |
| .setColumnNumber(loc.GetColumnNumber()) |
| .build(); |
| |
| auto frame = CallFrame::create() |
| .setCallFrameId(callFrameId) |
| .setFunctionName(toProtocolString( |
| m_isolate, iterator->GetFunctionDebugName())) |
| .setLocation(std::move(location)) |
| .setUrl(String16()) |
| .setScopeChain(std::move(scopes)) |
| .setThis(std::move(protocolReceiver)) |
| .setCanBeRestarted(iterator->CanBeRestarted()) |
| .build(); |
| |
| v8::debug::Location func_loc = iterator->GetFunctionLocation(); |
| if (!func_loc.IsEmpty()) { |
| frame->setFunctionLocation( |
| protocol::Debugger::Location::create() |
| .setScriptId(String16::fromInteger(script->Id())) |
| .setLineNumber(func_loc.GetLineNumber()) |
| .setColumnNumber(func_loc.GetColumnNumber()) |
| .build()); |
| } |
| |
| v8::Local<v8::Value> returnValue = iterator->GetReturnValue(); |
| if (!returnValue.IsEmpty() && injectedScript) { |
| std::unique_ptr<RemoteObject> value; |
| res = |
| injectedScript->wrapObject(returnValue, kBacktraceObjectGroup, |
| WrapOptions({WrapMode::kIdOnly}), &value); |
| if (!res.IsSuccess()) return res; |
| frame->setReturnValue(std::move(value)); |
| } |
| (*result)->emplace_back(std::move(frame)); |
| } |
| return Response::Success(); |
| } |
| |
| std::unique_ptr<protocol::Runtime::StackTrace> |
| V8DebuggerAgentImpl::currentAsyncStackTrace() { |
| std::shared_ptr<AsyncStackTrace> asyncParent = |
| m_debugger->currentAsyncParent(); |
| if (!asyncParent) return nullptr; |
| return asyncParent->buildInspectorObject( |
| m_debugger, m_debugger->maxAsyncCallChainDepth() - 1); |
| } |
| |
| std::unique_ptr<protocol::Runtime::StackTraceId> |
| V8DebuggerAgentImpl::currentExternalStackTrace() { |
| V8StackTraceId externalParent = m_debugger->currentExternalParent(); |
| if (externalParent.IsInvalid()) return nullptr; |
| return protocol::Runtime::StackTraceId::create() |
| .setId(stackTraceIdToString(externalParent.id)) |
| .setDebuggerId( |
| internal::V8DebuggerId(externalParent.debugger_id).toString()) |
| .build(); |
| } |
| |
| bool V8DebuggerAgentImpl::isPaused() const { |
| return m_debugger->isPausedInContextGroup(m_session->contextGroupId()); |
| } |
| |
| static String16 getScriptLanguage(const V8DebuggerScript& script) { |
| switch (script.getLanguage()) { |
| case V8DebuggerScript::Language::WebAssembly: |
| return protocol::Debugger::ScriptLanguageEnum::WebAssembly; |
| case V8DebuggerScript::Language::JavaScript: |
| return protocol::Debugger::ScriptLanguageEnum::JavaScript; |
| } |
| } |
| |
| #if V8_ENABLE_WEBASSEMBLY |
| static const char* getDebugSymbolTypeName( |
| v8::debug::WasmScript::DebugSymbolsType type) { |
| switch (type) { |
| case v8::debug::WasmScript::DebugSymbolsType::None: |
| return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum::None; |
| case v8::debug::WasmScript::DebugSymbolsType::SourceMap: |
| return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum:: |
| SourceMap; |
| case v8::debug::WasmScript::DebugSymbolsType::EmbeddedDWARF: |
| return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum:: |
| EmbeddedDWARF; |
| case v8::debug::WasmScript::DebugSymbolsType::ExternalDWARF: |
| return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum:: |
| ExternalDWARF; |
| } |
| } |
| |
| static std::unique_ptr<protocol::Debugger::DebugSymbols> getDebugSymbols( |
| const V8DebuggerScript& script) { |
| v8::debug::WasmScript::DebugSymbolsType type; |
| if (!script.getDebugSymbolsType().To(&type)) return {}; |
| |
| std::unique_ptr<protocol::Debugger::DebugSymbols> debugSymbols = |
| v8_inspector::protocol::Debugger::DebugSymbols::create() |
| .setType(getDebugSymbolTypeName(type)) |
| .build(); |
| String16 externalUrl; |
| if (script.getExternalDebugSymbolsURL().To(&externalUrl)) { |
| debugSymbols->setExternalURL(externalUrl); |
| } |
| return debugSymbols; |
| } |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| void V8DebuggerAgentImpl::didParseSource( |
| std::unique_ptr<V8DebuggerScript> script, bool success) { |
| v8::HandleScope handles(m_isolate); |
| if (!success) { |
| String16 scriptSource = script->source(0); |
| script->setSourceURL(findSourceURL(scriptSource, false)); |
| script->setSourceMappingURL(findSourceMapURL(scriptSource, false)); |
| } |
| |
| int contextId = script->executionContextId(); |
| int contextGroupId = m_inspector->contextGroupId(contextId); |
| InspectedContext* inspected = |
| m_inspector->getContext(contextGroupId, contextId); |
| std::unique_ptr<protocol::DictionaryValue> executionContextAuxData; |
| if (inspected) { |
| // Script reused between different groups/sessions can have a stale |
| // execution context id. |
| const String16& aux = inspected->auxData(); |
| std::vector<uint8_t> cbor; |
| v8_crdtp::json::ConvertJSONToCBOR( |
| v8_crdtp::span<uint16_t>(aux.characters16(), aux.length()), &cbor); |
| executionContextAuxData = protocol::DictionaryValue::cast( |
| protocol::Value::parseBinary(cbor.data(), cbor.size())); |
| } |
| bool isLiveEdit = script->isLiveEdit(); |
| bool hasSourceURLComment = script->hasSourceURLComment(); |
| bool isModule = script->isModule(); |
| String16 scriptId = script->scriptId(); |
| String16 scriptURL = script->sourceURL(); |
| String16 embedderName = script->embedderName(); |
| String16 scriptLanguage = getScriptLanguage(*script); |
| Maybe<int> codeOffset; |
| std::unique_ptr<protocol::Debugger::DebugSymbols> debugSymbols; |
| #if V8_ENABLE_WEBASSEMBLY |
| if (script->getLanguage() == V8DebuggerScript::Language::WebAssembly) |
| codeOffset = script->codeOffset(); |
| debugSymbols = getDebugSymbols(*script); |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| m_scripts[scriptId] = std::move(script); |
| // Release the strong reference to get notified when debugger is the only |
| // one that holds the script. Has to be done after script added to m_scripts. |
| m_scripts[scriptId]->MakeWeak(); |
| |
| ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId); |
| DCHECK(scriptIterator != m_scripts.end()); |
| V8DebuggerScript* scriptRef = scriptIterator->second.get(); |
| // V8 could create functions for parsed scripts before reporting and asks |
| // inspector about blackboxed state, we should reset state each time when we |
| // make any change that change isFunctionBlackboxed output - adding parsed |
| // script is changing. |
| scriptRef->resetBlackboxedStateCache(); |
| |
| Maybe<String16> sourceMapURLParam = scriptRef->sourceMappingURL(); |
| Maybe<protocol::DictionaryValue> executionContextAuxDataParam( |
| std::move(executionContextAuxData)); |
| const bool* isLiveEditParam = isLiveEdit ? &isLiveEdit : nullptr; |
| const bool* hasSourceURLParam = |
| hasSourceURLComment ? &hasSourceURLComment : nullptr; |
| const bool* isModuleParam = isModule ? &isModule : nullptr; |
| std::unique_ptr<V8StackTraceImpl> stack = |
| V8StackTraceImpl::capture(m_inspector->debugger(), 1); |
| std::unique_ptr<protocol::Runtime::StackTrace> stackTrace = |
| stack && !stack->isEmpty() |
| ? stack->buildInspectorObjectImpl(m_debugger, 0) |
| : nullptr; |
| |
| if (!success) { |
| m_frontend.scriptFailedToParse( |
| scriptId, scriptURL, scriptRef->startLine(), scriptRef->startColumn(), |
| scriptRef->endLine(), scriptRef->endColumn(), contextId, |
| scriptRef->hash(), std::move(executionContextAuxDataParam), |
| std::move(sourceMapURLParam), hasSourceURLParam, isModuleParam, |
| scriptRef->length(), std::move(stackTrace), std::move(codeOffset), |
| std::move(scriptLanguage), embedderName); |
| return; |
| } |
| |
| m_frontend.scriptParsed( |
| scriptId, scriptURL, scriptRef->startLine(), scriptRef->startColumn(), |
| scriptRef->endLine(), scriptRef->endColumn(), contextId, |
| scriptRef->hash(), std::move(executionContextAuxDataParam), |
| isLiveEditParam, std::move(sourceMapURLParam), hasSourceURLParam, |
| isModuleParam, scriptRef->length(), std::move(stackTrace), |
| std::move(codeOffset), std::move(scriptLanguage), std::move(debugSymbols), |
| embedderName); |
| |
| std::vector<protocol::DictionaryValue*> potentialBreakpoints; |
| if (!scriptURL.isEmpty()) { |
| protocol::DictionaryValue* breakpointsByUrl = |
| m_state->getObject(DebuggerAgentState::breakpointsByUrl); |
| if (breakpointsByUrl) { |
| potentialBreakpoints.push_back(breakpointsByUrl->getObject(scriptURL)); |
| } |
| potentialBreakpoints.push_back( |
| m_state->getObject(DebuggerAgentState::breakpointsByRegex)); |
| } |
| protocol::DictionaryValue* breakpointsByScriptHash = |
| m_state->getObject(DebuggerAgentState::breakpointsByScriptHash); |
| if (breakpointsByScriptHash) { |
| potentialBreakpoints.push_back( |
| breakpointsByScriptHash->getObject(scriptRef->hash())); |
| } |
| protocol::DictionaryValue* breakpointHints = |
| m_state->getObject(DebuggerAgentState::breakpointHints); |
| for (auto breakpoints : potentialBreakpoints) { |
| if (!breakpoints) continue; |
| for (size_t i = 0; i < breakpoints->size(); ++i) { |
| auto breakpointWithCondition = breakpoints->at(i); |
| String16 breakpointId = breakpointWithCondition.first; |
| |
| BreakpointType type; |
| String16 selector; |
| int lineNumber = 0; |
| int columnNumber = 0; |
| parseBreakpointId(breakpointId, &type, &selector, &lineNumber, |
| &columnNumber); |
| Matcher matcher(m_inspector, type, selector); |
| |
| if (!matcher.matches(*scriptRef)) continue; |
| // Make sure the session was not disabled by some re-entrant call |
| // in the script matcher. |
| DCHECK(enabled()); |
| String16 condition; |
| breakpointWithCondition.second->asString(&condition); |
| protocol::DictionaryValue* hint = |
| breakpointHints ? breakpointHints->getObject(breakpointId) : nullptr; |
| if (hint) { |
| adjustBreakpointLocation(*scriptRef, hint, &lineNumber, &columnNumber); |
| } |
| std::unique_ptr<protocol::Debugger::Location> location = |
| setBreakpointImpl(breakpointId, scriptId, condition, lineNumber, |
| columnNumber); |
| if (location) |
| m_frontend.breakpointResolved(breakpointId, std::move(location)); |
| } |
| } |
| setScriptInstrumentationBreakpointIfNeeded(scriptRef); |
| } |
| |
| void V8DebuggerAgentImpl::setScriptInstrumentationBreakpointIfNeeded( |
| V8DebuggerScript* scriptRef) { |
| protocol::DictionaryValue* breakpoints = |
| m_state->getObject(DebuggerAgentState::instrumentationBreakpoints); |
| if (!breakpoints) return; |
| bool isBlackboxed = isFunctionBlackboxed( |
| scriptRef->scriptId(), v8::debug::Location(0, 0), |
| v8::debug::Location(scriptRef->endLine(), scriptRef->endColumn())); |
| if (isBlackboxed) return; |
| |
| String16 sourceMapURL = scriptRef->sourceMappingURL(); |
| String16 breakpointId = generateInstrumentationBreakpointId( |
| InstrumentationEnum::BeforeScriptExecution); |
| if (!breakpoints->get(breakpointId)) { |
| if (sourceMapURL.isEmpty()) return; |
| breakpointId = generateInstrumentationBreakpointId( |
| InstrumentationEnum::BeforeScriptWithSourceMapExecution); |
| if (!breakpoints->get(breakpointId)) return; |
| } |
| v8::debug::BreakpointId debuggerBreakpointId; |
| if (!scriptRef->setInstrumentationBreakpoint(&debuggerBreakpointId)) return; |
| |
| m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId; |
| m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back( |
| debuggerBreakpointId); |
| } |
| |
| void V8DebuggerAgentImpl::didPauseOnInstrumentation( |
| v8::debug::BreakpointId instrumentationId) { |
| String16 breakReason = protocol::Debugger::Paused::ReasonEnum::Other; |
| std::unique_ptr<protocol::DictionaryValue> breakAuxData; |
| |
| std::unique_ptr<Array<CallFrame>> protocolCallFrames; |
| Response response = currentCallFrames(&protocolCallFrames); |
| if (!response.IsSuccess()) |
| protocolCallFrames = std::make_unique<Array<CallFrame>>(); |
| |
| if (m_debuggerBreakpointIdToBreakpointId.find(instrumentationId) != |
| m_debuggerBreakpointIdToBreakpointId.end()) { |
| DCHECK_GT(protocolCallFrames->size(), 0); |
| if (!protocolCallFrames->empty()) { |
| m_instrumentationFinished = false; |
| breakReason = protocol::Debugger::Paused::ReasonEnum::Instrumentation; |
| const String16 scriptId = |
| protocolCallFrames->at(0)->getLocation()->getScriptId(); |
| DCHECK_NE(m_scripts.find(scriptId), m_scripts.end()); |
| const auto& script = m_scripts[scriptId]; |
| |
| breakAuxData = protocol::DictionaryValue::create(); |
| breakAuxData->setString("scriptId", script->scriptId()); |
| breakAuxData->setString("url", script->sourceURL()); |
| if (!script->sourceMappingURL().isEmpty()) { |
| breakAuxData->setString("sourceMapURL", (script->sourceMappingURL())); |
| } |
| } |
| } |
| |
| m_frontend.paused(std::move(protocolCallFrames), breakReason, |
| std::move(breakAuxData), |
| std::make_unique<Array<String16>>(), |
| currentAsyncStackTrace(), currentExternalStackTrace()); |
| } |
| |
| void V8DebuggerAgentImpl::didPause( |
| int contextId, v8::Local<v8::Value> exception, |
| const std::vector<v8::debug::BreakpointId>& hitBreakpoints, |
| v8::debug::ExceptionType exceptionType, bool isUncaught, |
| v8::debug::BreakReasons breakReasons) { |
| v8::HandleScope handles(m_isolate); |
| |
| std::vector<BreakReason> hitReasons; |
| |
| if (breakReasons.contains(v8::debug::BreakReason::kOOM)) { |
| hitReasons.push_back( |
| std::make_pair(protocol::Debugger::Paused::ReasonEnum::OOM, nullptr)); |
| } else if (breakReasons.contains(v8::debug::BreakReason::kAssert)) { |
| hitReasons.push_back(std::make_pair( |
| protocol::Debugger::Paused::ReasonEnum::Assert, nullptr)); |
| } else if (breakReasons.contains(v8::debug::BreakReason::kException)) { |
| InjectedScript* injectedScript = nullptr; |
| m_session->findInjectedScript(contextId, injectedScript); |
| if (injectedScript) { |
| String16 breakReason = |
| exceptionType == v8::debug::kPromiseRejection |
| ? protocol::Debugger::Paused::ReasonEnum::PromiseRejection |
| : protocol::Debugger::Paused::ReasonEnum::Exception; |
| std::unique_ptr<protocol::Runtime::RemoteObject> obj; |
| injectedScript->wrapObject(exception, kBacktraceObjectGroup, |
| WrapOptions({WrapMode::kIdOnly}), &obj); |
| std::unique_ptr<protocol::DictionaryValue> breakAuxData; |
| if (obj) { |
| std::vector<uint8_t> serialized; |
| obj->AppendSerialized(&serialized); |
| breakAuxData = protocol::DictionaryValue::cast( |
| protocol::Value::parseBinary(serialized.data(), serialized.size())); |
| breakAuxData->setBoolean("uncaught", isUncaught); |
| } |
| hitReasons.push_back( |
| std::make_pair(breakReason, std::move(breakAuxData))); |
| } |
| } |
| |
| if (breakReasons.contains(v8::debug::BreakReason::kStep) || |
| breakReasons.contains(v8::debug::BreakReason::kAsyncStep)) { |
| hitReasons.push_back( |
| std::make_pair(protocol::Debugger::Paused::ReasonEnum::Step, nullptr)); |
| } |
| |
| auto hitBreakpointIds = std::make_unique<Array<String16>>(); |
| bool hitRegularBreakpoint = false; |
| for (const auto& id : hitBreakpoints) { |
| auto breakpointIterator = m_debuggerBreakpointIdToBreakpointId.find(id); |
| if (breakpointIterator == m_debuggerBreakpointIdToBreakpointId.end()) { |
| continue; |
| } |
| const String16& breakpointId = breakpointIterator->second; |
| hitBreakpointIds->emplace_back(breakpointId); |
| BreakpointType type; |
| parseBreakpointId(breakpointId, &type); |
| if (type == BreakpointType::kDebugCommand) { |
| hitReasons.push_back(std::make_pair( |
| protocol::Debugger::Paused::ReasonEnum::DebugCommand, nullptr)); |
| } else { |
| hitRegularBreakpoint = true; |
| } |
| } |
| |
| for (size_t i = 0; i < m_breakReason.size(); ++i) { |
| hitReasons.push_back(std::move(m_breakReason[i])); |
| } |
| clearBreakDetails(); |
| |
| // Make sure that we only include (other: nullptr) once. |
| const BreakReason otherHitReason = |
| std::make_pair(protocol::Debugger::Paused::ReasonEnum::Other, nullptr); |
| const bool otherBreakReasons = |
| hitRegularBreakpoint || hitBreakReasonEncodedAsOther(breakReasons); |
| if (otherBreakReasons && std::find(hitReasons.begin(), hitReasons.end(), |
| otherHitReason) == hitReasons.end()) { |
| hitReasons.push_back( |
| std::make_pair(protocol::Debugger::Paused::ReasonEnum::Other, nullptr)); |
| } |
| |
| // We should always know why we pause: either the pause relates to this agent |
| // (`hitReason` is non empty), or it relates to another agent (hit a |
| // breakpoint there, or a triggered pause was scheduled by other agent). |
| DCHECK(hitReasons.size() > 0 || !hitBreakpoints.empty() || |
| breakReasons.contains(v8::debug::BreakReason::kAgent)); |
| String16 breakReason = protocol::Debugger::Paused::ReasonEnum::Other; |
| std::unique_ptr<protocol::DictionaryValue> breakAuxData; |
| if (hitReasons.size() == 1) { |
| breakReason = hitReasons[0].first; |
| breakAuxData = std::move(hitReasons[0].second); |
| } else if (hitReasons.size() > 1) { |
| breakReason = protocol::Debugger::Paused::ReasonEnum::Ambiguous; |
| std::unique_ptr<protocol::ListValue> reasons = |
| protocol::ListValue::create(); |
| for (size_t i = 0; i < hitReasons.size(); ++i) { |
| std::unique_ptr<protocol::DictionaryValue> reason = |
| protocol::DictionaryValue::create(); |
| reason->setString("reason", hitReasons[i].first); |
| if (hitReasons[i].second) |
| reason->setObject("auxData", std::move(hitReasons[i].second)); |
| reasons->pushValue(std::move(reason)); |
| } |
| breakAuxData = protocol::DictionaryValue::create(); |
| breakAuxData->setArray("reasons", std::move(reasons)); |
| } |
| |
| std::unique_ptr<Array<CallFrame>> protocolCallFrames; |
| Response response = currentCallFrames(&protocolCallFrames); |
| if (!response.IsSuccess()) |
| protocolCallFrames = std::make_unique<Array<CallFrame>>(); |
| |
| v8::debug::NotifyDebuggerPausedEventSent(m_debugger->isolate()); |
| m_frontend.paused(std::move(protocolCallFrames), breakReason, |
| std::move(breakAuxData), std::move(hitBreakpointIds), |
| currentAsyncStackTrace(), currentExternalStackTrace()); |
| } |
| |
| void V8DebuggerAgentImpl::didContinue() { |
| m_frontend.resumed(); |
| m_frontend.flush(); |
| } |
| |
| void V8DebuggerAgentImpl::breakProgram( |
| const String16& breakReason, |
| std::unique_ptr<protocol::DictionaryValue> data) { |
| if (!enabled() || m_skipAllPauses || !m_debugger->canBreakProgram()) return; |
| std::vector<BreakReason> currentScheduledReason; |
| currentScheduledReason.swap(m_breakReason); |
| pushBreakDetails(breakReason, std::move(data)); |
| |
| int contextGroupId = m_session->contextGroupId(); |
| int sessionId = m_session->sessionId(); |
| V8InspectorImpl* inspector = m_inspector; |
| m_debugger->breakProgram(contextGroupId); |
| // Check that session and |this| are still around. |
| if (!inspector->sessionById(contextGroupId, sessionId)) return; |
| if (!enabled()) return; |
| |
| popBreakDetails(); |
| m_breakReason.swap(currentScheduledReason); |
| if (!m_breakReason.empty()) { |
| m_debugger->setPauseOnNextCall(true, m_session->contextGroupId()); |
| } |
| } |
| |
| void V8DebuggerAgentImpl::setBreakpointFor(v8::Local<v8::Function> function, |
| v8::Local<v8::String> condition, |
| BreakpointSource source) { |
| String16 breakpointId = generateBreakpointId( |
| source == DebugCommandBreakpointSource ? BreakpointType::kDebugCommand |
| : BreakpointType::kMonitorCommand, |
| function); |
| if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) != |
| m_breakpointIdToDebuggerBreakpointIds.end()) { |
| return; |
| } |
| setBreakpointImpl(breakpointId, function, condition); |
| } |
| |
| void V8DebuggerAgentImpl::removeBreakpointFor(v8::Local<v8::Function> function, |
| BreakpointSource source) { |
| String16 breakpointId = generateBreakpointId( |
| source == DebugCommandBreakpointSource ? BreakpointType::kDebugCommand |
| : BreakpointType::kMonitorCommand, |
| function); |
| std::vector<V8DebuggerScript*> scripts; |
| removeBreakpointImpl(breakpointId, scripts); |
| } |
| |
| void V8DebuggerAgentImpl::reset() { |
| if (!enabled()) return; |
| m_blackboxedPositions.clear(); |
| resetBlackboxedStateCache(); |
| m_skipList.clear(); |
| m_scripts.clear(); |
| m_cachedScripts.clear(); |
| m_cachedScriptSize = 0; |
| m_debugger->allAsyncTasksCanceled(); |
| } |
| |
| void V8DebuggerAgentImpl::ScriptCollected(const V8DebuggerScript* script) { |
| DCHECK_NE(m_scripts.find(script->scriptId()), m_scripts.end()); |
| std::vector<uint8_t> bytecode; |
| #if V8_ENABLE_WEBASSEMBLY |
| v8::MemorySpan<const uint8_t> span; |
| if (script->wasmBytecode().To(&span)) { |
| bytecode.reserve(span.size()); |
| bytecode.insert(bytecode.begin(), span.data(), span.data() + span.size()); |
| } |
| #endif |
| CachedScript cachedScript{script->scriptId(), script->source(0), |
| std::move(bytecode)}; |
| m_cachedScriptSize += cachedScript.size(); |
| m_cachedScripts.push_back(std::move(cachedScript)); |
| m_scripts.erase(script->scriptId()); |
| |
| while (m_cachedScriptSize > m_maxScriptCacheSize) { |
| const CachedScript& cachedScript = m_cachedScripts.front(); |
| DCHECK_GE(m_cachedScriptSize, cachedScript.size()); |
| m_cachedScriptSize -= cachedScript.size(); |
| m_cachedScripts.pop_front(); |
| } |
| } |
| |
| Response V8DebuggerAgentImpl::processSkipList( |
| protocol::Array<protocol::Debugger::LocationRange>& skipList) { |
| std::unordered_map<String16, std::vector<std::pair<int, int>>> skipListInit; |
| for (std::unique_ptr<protocol::Debugger::LocationRange>& range : skipList) { |
| protocol::Debugger::ScriptPosition* start = range->getStart(); |
| protocol::Debugger::ScriptPosition* end = range->getEnd(); |
| String16 scriptId = range->getScriptId(); |
| |
| auto it = m_scripts.find(scriptId); |
| if (it == m_scripts.end()) |
| return Response::ServerError("No script with passed id."); |
| |
| Response res = isValidPosition(start); |
| if (res.IsError()) return res; |
| |
| res = isValidPosition(end); |
| if (res.IsError()) return res; |
| |
| skipListInit[scriptId].emplace_back(start->getLineNumber(), |
| start->getColumnNumber()); |
| skipListInit[scriptId].emplace_back(end->getLineNumber(), |
| end->getColumnNumber()); |
| } |
| |
| // Verify that the skipList is sorted, and that all ranges |
| // are properly defined (start comes before end). |
| for (auto skipListPair : skipListInit) { |
| Response res = isValidRangeOfPositions(skipListPair.second); |
| if (res.IsError()) return res; |
| } |
| |
| m_skipList = std::move(skipListInit); |
| return Response::Success(); |
| } |
| |
| void V8DebuggerAgentImpl::stop() { |
| disable(); |
| m_enableState = kStopping; |
| } |
| } // namespace v8_inspector |