Upgrade V8 to 8.8.278.14
Bug: 162604069
Bug: 167389063
Test: gts-tradefed run gts-dev --module GtsGmscoreHostTestCases
--test com.google.android.gts.devicepolicy.DeviceOwnerTest#testProxyPacProxyTest
Test: m -j proxy_resolver_v8_unittest && adb sync && adb shell \
/data/nativetest/proxy_resolver_v8_unittest/proxy_resolver_v8_unittest
Merged-In: Ifb09923b9d7f6d8990fb062d7dc0294edf2c098e
Change-Id: Ifb09923b9d7f6d8990fb062d7dc0294edf2c098e
(cherry picked from commit 9580a23bc5b8874a0979001d3595d027cbb68128)
diff --git a/src/inspector/v8-debugger-agent-impl.cc b/src/inspector/v8-debugger-agent-impl.cc
index 7de46a1..f82ce98 100644
--- a/src/inspector/v8-debugger-agent-impl.cc
+++ b/src/inspector/v8-debugger-agent-impl.cc
@@ -6,13 +6,15 @@
#include <algorithm>
+#include "../../third_party/inspector_protocol/crdtp/json.h"
+#include "include/v8-inspector.h"
+#include "src/base/safe_conversions.h"
#include "src/debug/debug-interface.h"
#include "src/inspector/injected-script.h"
#include "src/inspector/inspected-context.h"
-#include "src/inspector/java-script-call-frame.h"
+#include "src/inspector/protocol/Debugger.h"
#include "src/inspector/protocol/Protocol.h"
#include "src/inspector/remote-object-id.h"
-#include "src/inspector/script-breakpoint.h"
#include "src/inspector/search-util.h"
#include "src/inspector/string-util.h"
#include "src/inspector/v8-debugger-script.h"
@@ -22,9 +24,7 @@
#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-copier.h"
-
-#include "include/v8-inspector.h"
+#include "src/inspector/v8-value-utils.h"
namespace v8_inspector {
@@ -32,26 +32,27 @@
using protocol::Maybe;
using protocol::Debugger::BreakpointId;
using protocol::Debugger::CallFrame;
+using protocol::Debugger::Scope;
using protocol::Runtime::ExceptionDetails;
-using protocol::Runtime::ScriptId;
-using protocol::Runtime::StackTrace;
using protocol::Runtime::RemoteObject;
+using protocol::Runtime::ScriptId;
+
+namespace InstrumentationEnum =
+ protocol::Debugger::SetInstrumentationBreakpoint::InstrumentationEnum;
namespace DebuggerAgentState {
-static const char javaScriptBreakpoints[] = "javaScriptBreakopints";
static const char pauseOnExceptionsState[] = "pauseOnExceptionsState";
static const char asyncCallStackDepth[] = "asyncCallStackDepth";
static const char blackboxPattern[] = "blackboxPattern";
static const char debuggerEnabled[] = "debuggerEnabled";
-
-// Breakpoint properties.
-static const char url[] = "url";
-static const char isRegex[] = "isRegex";
-static const char lineNumber[] = "lineNumber";
-static const char columnNumber[] = "columnNumber";
-static const char condition[] = "condition";
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 instrumentationBreakpoints[] = "instrumentationBreakpoints";
+
} // namespace DebuggerAgentState
static const char kBacktraceObjectGroup[] = "backtrace";
@@ -59,66 +60,284 @@
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;
+
+// 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 const size_t kWasmBytecodeMaxLength = (v8::String::kMaxLength / 4) * 3;
+static const char kWasmBytecodeExceedsTransferLimit[] =
+ "WebAssembly bytecode exceeds the transfer limit";
+
namespace {
-void TranslateWasmStackTraceLocations(Array<CallFrame>* stackTrace,
- WasmTranslation* wasmTranslation) {
- for (size_t i = 0, e = stackTrace->length(); i != e; ++i) {
- protocol::Debugger::Location* location = stackTrace->get(i)->getLocation();
- String16 scriptId = location->getScriptId();
- int lineNumber = location->getLineNumber();
- int columnNumber = location->getColumnNumber(-1);
+enum class BreakpointType {
+ kByUrl = 1,
+ kByUrlRegex,
+ kByScriptHash,
+ kByScriptId,
+ kDebugCommand,
+ kMonitorCommand,
+ kBreakpointAtEntry,
+ kInstrumentationBreakpoint
+};
- if (!wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
- &scriptId, &lineNumber, &columnNumber)) {
- continue;
- }
-
- location->setScriptId(std::move(scriptId));
- location->setLineNumber(lineNumber);
- location->setColumnNumber(columnNumber);
- }
-}
-
-String16 breakpointIdSuffix(V8DebuggerAgentImpl::BreakpointSource source) {
- switch (source) {
- case V8DebuggerAgentImpl::UserBreakpointSource:
- break;
- case V8DebuggerAgentImpl::DebugCommandBreakpointSource:
- return ":debug";
- case V8DebuggerAgentImpl::MonitorCommandBreakpointSource:
- return ":monitor";
- }
- return String16();
-}
-
-String16 generateBreakpointId(const ScriptBreakpoint& breakpoint,
- V8DebuggerAgentImpl::BreakpointSource source) {
+String16 generateBreakpointId(BreakpointType type,
+ const String16& scriptSelector, int lineNumber,
+ int columnNumber) {
String16Builder builder;
- builder.append(breakpoint.script_id);
+ builder.appendNumber(static_cast<int>(type));
builder.append(':');
- builder.appendNumber(breakpoint.line_number);
+ builder.appendNumber(lineNumber);
builder.append(':');
- builder.appendNumber(breakpoint.column_number);
- builder.append(breakpointIdSuffix(source));
+ 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::Debugger::Location> buildProtocolLocation(
- const String16& scriptId, int lineNumber, int columnNumber) {
- return protocol::Debugger::Location::create()
- .setScriptId(scriptId)
- .setLineNumber(lineNumber)
- .setColumnNumber(columnNumber)
- .build();
+String16 breakpointHint(const V8DebuggerScript& script, int lineNumber,
+ int columnNumber) {
+ int offset = script.offset(lineNumber, columnNumber);
+ if (offset == V8DebuggerScript::kNoOffset) return String16();
+ String16 hint =
+ script.source(offset, kBreakpointHintMaxLength).stripWhiteSpace();
+ for (size_t i = 0; i < hint.length(); ++i) {
+ if (hint[i] == '\r' || hint[i] == '\n' || hint[i] == ';') {
+ return hint.substring(0, i);
+ }
+ }
+ return hint;
}
+void adjustBreakpointLocation(const V8DebuggerScript& script,
+ const String16& hint, int* lineNumber,
+ int* columnNumber) {
+ if (*lineNumber < script.startLine() || *lineNumber > script.endLine())
+ return;
+ if (hint.isEmpty()) return;
+ intptr_t sourceOffset = script.offset(*lineNumber, *columnNumber);
+ if (sourceOffset == V8DebuggerScript::kNoOffset) return;
+
+ intptr_t searchRegionOffset = std::max(
+ sourceOffset - kBreakpointHintMaxSearchOffset, static_cast<intptr_t>(0));
+ size_t offset = sourceOffset - searchRegionOffset;
+ String16 searchArea = script.source(searchRegionOffset,
+ offset + kBreakpointHintMaxSearchOffset);
+
+ 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) {
+ 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();
+ return String16();
+}
+
+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,
+ WrapMode::kNoPreview, &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();
+}
} // namespace
V8DebuggerAgentImpl::V8DebuggerAgentImpl(
@@ -130,70 +349,83 @@
m_enabled(false),
m_state(state),
m_frontend(frontendChannel),
- m_isolate(m_inspector->isolate()),
- m_scheduledDebuggerStep(NoStep),
- m_javaScriptPauseScheduled(false),
- m_recursionLevelForStepOut(0) {
-}
+ m_isolate(m_inspector->isolate()) {}
-V8DebuggerAgentImpl::~V8DebuggerAgentImpl() {}
+V8DebuggerAgentImpl::~V8DebuggerAgentImpl() = default;
void V8DebuggerAgentImpl::enableImpl() {
m_enabled = true;
m_state->setBoolean(DebuggerAgentState::debuggerEnabled, true);
m_debugger->enable();
- std::vector<std::unique_ptr<V8DebuggerScript>> compiledScripts;
- m_debugger->getCompiledScripts(m_session->contextGroupId(), compiledScripts);
- for (size_t i = 0; i < compiledScripts.size(); i++)
- didParseSource(std::move(compiledScripts[i]), true);
+ std::vector<std::unique_ptr<V8DebuggerScript>> compiledScripts =
+ m_debugger->getCompiledScripts(m_session->contextGroupId(), this);
+ for (auto& script : compiledScripts) {
+ didParseSource(std::move(script), true);
+ }
- // FIXME(WK44513): breakpoints activated flag should be synchronized between
- // all front-ends
- m_debugger->setBreakpointsActivated(true);
+ m_breakpointsActive = true;
+ m_debugger->setBreakpointsActive(true);
+
+ if (isPaused()) {
+ didPause(0, v8::Local<v8::Value>(), std::vector<v8::debug::BreakpointId>(),
+ v8::debug::kException, false, false, false);
+ }
}
-bool V8DebuggerAgentImpl::enabled() { return m_enabled; }
-
-Response V8DebuggerAgentImpl::enable() {
- if (enabled()) return Response::OK();
+Response V8DebuggerAgentImpl::enable(Maybe<double> maxScriptsCacheSize,
+ String16* outDebuggerId) {
+ m_maxScriptCacheSize = v8::base::saturated_cast<size_t>(
+ maxScriptsCacheSize.fromMaybe(std::numeric_limits<double>::max()));
+ *outDebuggerId =
+ m_debugger->debuggerIdFor(m_session->contextGroupId()).toString();
+ if (enabled()) return Response::Success();
if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId()))
- return Response::Error("Script execution is prohibited");
+ return Response::ServerError("Script execution is prohibited");
enableImpl();
- return Response::OK();
+ return Response::Success();
}
Response V8DebuggerAgentImpl::disable() {
- if (!enabled()) return Response::OK();
+ if (!enabled()) return Response::Success();
- m_state->setObject(DebuggerAgentState::javaScriptBreakpoints,
- protocol::DictionaryValue::create());
+ 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 (isPaused()) m_debugger->continueProgram();
- m_debugger->disable();
- JavaScriptCallFrames emptyCallFrames;
- m_pausedCallFrames.swap(emptyCallFrames);
+ if (m_breakpointsActive) {
+ m_debugger->setBreakpointsActive(false);
+ m_breakpointsActive = false;
+ }
m_blackboxedPositions.clear();
m_blackboxPattern.reset();
resetBlackboxedStateCache();
+ m_skipList.clear();
m_scripts.clear();
+ m_cachedScriptIds.clear();
+ m_cachedScriptSize = 0;
+ for (const auto& it : m_debuggerBreakpointIdToBreakpointId) {
+ v8::debug::RemoveBreakpoint(m_isolate, it.first);
+ }
m_breakpointIdToDebuggerBreakpointIds.clear();
+ m_debuggerBreakpointIdToBreakpointId.clear();
m_debugger->setAsyncCallStackDepth(this, 0);
- m_continueToLocationBreakpointId = String16();
clearBreakDetails();
- m_scheduledDebuggerStep = NoStep;
- m_javaScriptPauseScheduled = false;
m_skipAllPauses = false;
m_state->setBoolean(DebuggerAgentState::skipAllPauses, false);
m_state->remove(DebuggerAgentState::blackboxPattern);
m_enabled = false;
m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false);
- return Response::OK();
+ m_debugger->disable();
+ return Response::Success();
}
void V8DebuggerAgentImpl::restore() {
@@ -225,148 +457,273 @@
}
Response V8DebuggerAgentImpl::setBreakpointsActive(bool active) {
- if (!enabled()) return Response::Error(kDebuggerNotEnabled);
- m_debugger->setBreakpointsActivated(active);
- return Response::OK();
+ if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
+ 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::OK();
+ return Response::Success();
}
-static std::unique_ptr<protocol::DictionaryValue>
-buildObjectForBreakpointCookie(const String16& url, int lineNumber,
- int columnNumber, const String16& condition,
- bool isRegex) {
- std::unique_ptr<protocol::DictionaryValue> breakpointObject =
- protocol::DictionaryValue::create();
- breakpointObject->setString(DebuggerAgentState::url, url);
- breakpointObject->setInteger(DebuggerAgentState::lineNumber, lineNumber);
- breakpointObject->setInteger(DebuggerAgentState::columnNumber, columnNumber);
- breakpointObject->setString(DebuggerAgentState::condition, condition);
- breakpointObject->setBoolean(DebuggerAgentState::isRegex, isRegex);
- return breakpointObject;
-}
-
-static bool matches(V8InspectorImpl* inspector, const String16& url,
- const String16& pattern, bool isRegex) {
- if (isRegex) {
- V8Regex regex(inspector, pattern, true);
- return regex.match(url) != -1;
+static bool matches(V8InspectorImpl* inspector, const V8DebuggerScript& script,
+ BreakpointType type, const String16& selector) {
+ switch (type) {
+ case BreakpointType::kByUrl:
+ return script.sourceURL() == selector;
+ case BreakpointType::kByScriptHash:
+ return script.hash() == selector;
+ case BreakpointType::kByUrlRegex: {
+ V8Regex regex(inspector, selector, true);
+ return regex.match(script.sourceURL()) != -1;
+ }
+ case BreakpointType::kByScriptId: {
+ return script.scriptId() == selector;
+ }
+ default:
+ return false;
}
- return url == pattern;
}
Response V8DebuggerAgentImpl::setBreakpointByUrl(
int lineNumber, Maybe<String16> optionalURL,
- Maybe<String16> optionalURLRegex, Maybe<int> optionalColumnNumber,
- Maybe<String16> optionalCondition, String16* outBreakpointId,
+ Maybe<String16> optionalURLRegex, Maybe<String16> optionalScriptHash,
+ Maybe<int> optionalColumnNumber, Maybe<String16> optionalCondition,
+ String16* outBreakpointId,
std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) {
- *locations = Array<protocol::Debugger::Location>::create();
- if (optionalURL.isJust() == optionalURLRegex.isJust())
- return Response::Error("Either url or urlRegex must be specified.");
+ *locations = std::make_unique<Array<protocol::Debugger::Location>>();
- String16 url = optionalURL.isJust() ? optionalURL.fromJust()
- : optionalURLRegex.fromJust();
+ int specified = (optionalURL.isJust() ? 1 : 0) +
+ (optionalURLRegex.isJust() ? 1 : 0) +
+ (optionalScriptHash.isJust() ? 1 : 0);
+ if (specified != 1) {
+ return Response::ServerError(
+ "Either url or urlRegex or scriptHash must be specified.");
+ }
int columnNumber = 0;
if (optionalColumnNumber.isJust()) {
columnNumber = optionalColumnNumber.fromJust();
- if (columnNumber < 0) return Response::Error("Incorrect column number");
+ if (columnNumber < 0)
+ return Response::ServerError("Incorrect column number");
}
- String16 condition = optionalCondition.fromMaybe("");
- bool isRegex = optionalURLRegex.isJust();
- String16 breakpointId = (isRegex ? "/" + url + "/" : url) + ":" +
- String16::fromInteger(lineNumber) + ":" +
- String16::fromInteger(columnNumber);
- protocol::DictionaryValue* breakpointsCookie =
- m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
- if (!breakpointsCookie) {
- std::unique_ptr<protocol::DictionaryValue> newValue =
- protocol::DictionaryValue::create();
- breakpointsCookie = newValue.get();
- m_state->setObject(DebuggerAgentState::javaScriptBreakpoints,
- std::move(newValue));
+ BreakpointType type = BreakpointType::kByUrl;
+ String16 selector;
+ if (optionalURLRegex.isJust()) {
+ selector = optionalURLRegex.fromJust();
+ type = BreakpointType::kByUrlRegex;
+ } else if (optionalURL.isJust()) {
+ selector = optionalURL.fromJust();
+ type = BreakpointType::kByUrl;
+ } else if (optionalScriptHash.isJust()) {
+ selector = optionalScriptHash.fromJust();
+ type = BreakpointType::kByScriptHash;
}
- if (breakpointsCookie->get(breakpointId))
- return Response::Error("Breakpoint at specified location already exists.");
- breakpointsCookie->setObject(
- breakpointId, buildObjectForBreakpointCookie(
- url, lineNumber, columnNumber, condition, isRegex));
+ String16 condition = optionalCondition.fromMaybe(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.");
+ }
- ScriptBreakpoint breakpoint(String16(), lineNumber, columnNumber, condition);
+ String16 hint;
for (const auto& script : m_scripts) {
- if (!matches(m_inspector, script.second->sourceURL(), url, isRegex))
- continue;
- breakpoint.script_id = script.first;
- std::unique_ptr<protocol::Debugger::Location> location =
- resolveBreakpoint(breakpointId, breakpoint, UserBreakpointSource);
- if (location) (*locations)->addItem(std::move(location));
+ if (!matches(m_inspector, *script.second, type, selector)) continue;
+ if (!hint.isEmpty()) {
+ adjustBreakpointLocation(*script.second, hint, &lineNumber,
+ &columnNumber);
+ }
+ std::unique_ptr<protocol::Debugger::Location> location = setBreakpointImpl(
+ breakpointId, script.first, condition, lineNumber, columnNumber);
+ if (location && type != BreakpointType::kByUrlRegex) {
+ hint = breakpointHint(*script.second, lineNumber, columnNumber);
+ }
+ if (location) (*locations)->emplace_back(std::move(location));
}
-
+ breakpoints->setString(breakpointId, condition);
+ if (!hint.isEmpty()) {
+ protocol::DictionaryValue* breakpointHints =
+ getOrCreateObject(m_state, DebuggerAgentState::breakpointHints);
+ breakpointHints->setString(breakpointId, hint);
+ }
*outBreakpointId = breakpointId;
- return Response::OK();
+ 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) {
- ScriptBreakpoint breakpoint(
- location->getScriptId(), location->getLineNumber(),
- location->getColumnNumber(0), optionalCondition.fromMaybe(String16()));
-
- String16 breakpointId =
- generateBreakpointId(breakpoint, UserBreakpointSource);
+ String16 breakpointId = generateBreakpointId(
+ BreakpointType::kByScriptId, location->getScriptId(),
+ location->getLineNumber(), location->getColumnNumber(0));
if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) !=
m_breakpointIdToDebuggerBreakpointIds.end()) {
- return Response::Error("Breakpoint at specified location already exists.");
+ return Response::ServerError(
+ "Breakpoint at specified location already exists.");
}
- *actualLocation =
- resolveBreakpoint(breakpointId, breakpoint, UserBreakpointSource);
- if (!*actualLocation) return Response::Error("Could not resolve breakpoint");
+ *actualLocation = setBreakpointImpl(breakpointId, location->getScriptId(),
+ optionalCondition.fromMaybe(String16()),
+ location->getLineNumber(),
+ location->getColumnNumber(0));
+ if (!*actualLocation)
+ return Response::ServerError("Could not resolve breakpoint");
*outBreakpointId = breakpointId;
- return Response::OK();
+ return Response::Success();
+}
+
+Response V8DebuggerAgentImpl::setBreakpointOnFunctionCall(
+ const String16& functionObjectId, Maybe<String16> optionalCondition,
+ String16* outBreakpointId) {
+ 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.fromMaybe(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::Error(kDebuggerNotEnabled);
- protocol::DictionaryValue* breakpointsCookie =
- m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
- if (breakpointsCookie) breakpointsCookie->remove(breakpointId);
- removeBreakpointImpl(breakpointId);
- return Response::OK();
+ if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
+ BreakpointType type;
+ String16 selector;
+ if (!parseBreakpointId(breakpointId, &type, &selector)) {
+ return Response::Success();
+ }
+ 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) {
+ if (!matches(m_inspector, *scriptIter.second, type, selector)) continue;
+ V8DebuggerScript* script = scriptIter.second.get();
+ scripts.push_back(script);
+ }
+ removeBreakpointImpl(breakpointId, scripts);
+
+ return Response::Success();
}
-void V8DebuggerAgentImpl::removeBreakpointImpl(const String16& breakpointId) {
+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())
+ m_breakpointIdToDebuggerBreakpointIds.end()) {
return;
- const std::vector<String16>& ids = debuggerBreakpointIdsIterator->second;
- for (size_t i = 0; i < ids.size(); ++i) {
- const String16& debuggerBreakpointId = ids[i];
-
- m_debugger->removeBreakpoint(debuggerBreakpointId);
- m_serverBreakpoints.erase(debuggerBreakpointId);
+ }
+ for (const auto& id : debuggerBreakpointIdsIterator->second) {
+ for (auto& script : scripts) {
+ script->removeWasmBreakpoint(id);
+ }
+ v8::debug::RemoveBreakpoint(m_isolate, id);
+ m_debuggerBreakpointIdToBreakpointId.erase(id);
}
m_breakpointIdToDebuggerBreakpointIds.erase(breakpointId);
}
Response V8DebuggerAgentImpl::getPossibleBreakpoints(
std::unique_ptr<protocol::Debugger::Location> start,
- Maybe<protocol::Debugger::Location> end,
- std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) {
+ 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::Error(
+ return Response::ServerError(
"start.lineNumber and start.columnNumber should be >= 0");
v8::debug::Location v8Start(start->getLineNumber(),
@@ -374,49 +731,104 @@
v8::debug::Location v8End;
if (end.isJust()) {
if (end.fromJust()->getScriptId() != scriptId)
- return Response::Error("Locations should contain the same scriptId");
+ return Response::ServerError(
+ "Locations should contain the same scriptId");
int line = end.fromJust()->getLineNumber();
int column = end.fromJust()->getColumnNumber(0);
if (line < 0 || column < 0)
- return Response::Error(
+ 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::Error("Script not found");
-
- std::vector<v8::debug::Location> v8Locations;
- if (!it->second->getPossibleBreakpoints(v8Start, v8End, &v8Locations))
- return Response::InternalError();
-
- *locations = protocol::Array<protocol::Debugger::Location>::create();
- for (size_t i = 0; i < v8Locations.size(); ++i) {
- (*locations)
- ->addItem(protocol::Debugger::Location::create()
- .setScriptId(scriptId)
- .setLineNumber(v8Locations[i].GetLineNumber())
- .setColumnNumber(v8Locations[i].GetColumnNumber())
- .build());
+ 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(m_isolate,
+ v8::MicrotasksScope::kDoNotRunMicrotasks);
+ v8::TryCatch tryCatch(m_isolate);
+ it->second->getPossibleBreakpoints(
+ v8Start, v8End, restrictToFunction.fromMaybe(false), &v8Locations);
}
- return Response::OK();
+
+ *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) {
- if (!enabled()) return Response::Error(kDebuggerNotEnabled);
- if (!m_continueToLocationBreakpointId.isEmpty()) {
- m_debugger->removeBreakpoint(m_continueToLocationBreakpointId);
- m_continueToLocationBreakpointId = "";
+ 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.fromMaybe(
+ protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any));
+}
- ScriptBreakpoint breakpoint(location->getScriptId(),
- location->getLineNumber(),
- location->getColumnNumber(0), String16());
+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");
- m_continueToLocationBreakpointId = m_debugger->setBreakpoint(
- breakpoint, &breakpoint.line_number, &breakpoint.column_number);
- // TODO(kozyatinskiy): Return actual line and column number.
- return resume();
+ V8DebuggerId debuggerId;
+ if (inStackTraceId->hasDebuggerId()) {
+ debuggerId = 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,
@@ -453,45 +865,87 @@
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::resolveBreakpoint(const String16& breakpointId,
- const ScriptBreakpoint& breakpoint,
- BreakpointSource source) {
+V8DebuggerAgentImpl::setBreakpointImpl(const String16& breakpointId,
+ const String16& scriptId,
+ const String16& condition,
+ int lineNumber, int columnNumber) {
v8::HandleScope handles(m_isolate);
DCHECK(enabled());
- // FIXME: remove these checks once crbug.com/520702 is resolved.
- CHECK(!breakpointId.isEmpty());
- CHECK(!breakpoint.script_id.isEmpty());
- ScriptsMap::iterator scriptIterator = m_scripts.find(breakpoint.script_id);
+
+ ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId);
if (scriptIterator == m_scripts.end()) return nullptr;
- if (breakpoint.line_number < scriptIterator->second->startLine() ||
- scriptIterator->second->endLine() < breakpoint.line_number)
+ V8DebuggerScript* script = scriptIterator->second.get();
+ if (lineNumber < script->startLine() || script->endLine() < lineNumber) {
return nullptr;
+ }
- // Translate from protocol location to v8 location for the debugger.
- ScriptBreakpoint translatedBreakpoint = breakpoint;
- m_debugger->wasmTranslation()->TranslateProtocolLocationToWasmScriptLocation(
- &translatedBreakpoint.script_id, &translatedBreakpoint.line_number,
- &translatedBreakpoint.column_number);
+ v8::debug::BreakpointId debuggerBreakpointId;
+ v8::debug::Location location(lineNumber, columnNumber);
+ int contextId = script->executionContextId();
+ InspectedContext* inspected = m_inspector->getContext(contextId);
+ if (!inspected) return nullptr;
- int actualLineNumber;
- int actualColumnNumber;
- String16 debuggerBreakpointId = m_debugger->setBreakpoint(
- translatedBreakpoint, &actualLineNumber, &actualColumnNumber);
- if (debuggerBreakpointId.isEmpty()) return nullptr;
+ {
+ v8::Context::Scope contextScope(inspected->context());
+ if (!script->setBreakpoint(condition, &location, &debuggerBreakpointId)) {
+ return nullptr;
+ }
+ }
- // Translate back from v8 location to protocol location for the return value.
- m_debugger->wasmTranslation()->TranslateWasmScriptLocationToProtocolLocation(
- &translatedBreakpoint.script_id, &actualLineNumber, &actualColumnNumber);
-
- m_serverBreakpoints[debuggerBreakpointId] =
- std::make_pair(breakpointId, source);
- CHECK(!breakpointId.isEmpty());
-
+ m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId;
m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back(
debuggerBreakpointId);
- return buildProtocolLocation(translatedBreakpoint.script_id, actualLineNumber,
- actualColumnNumber);
+
+ 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(
@@ -501,90 +955,119 @@
v8::HandleScope handles(m_isolate);
ScriptsMap::iterator it = m_scripts.find(scriptId);
if (it == m_scripts.end())
- return Response::Error("No script for id: " + scriptId);
+ return Response::ServerError("No script for id: " + scriptId.utf8());
- std::vector<std::unique_ptr<protocol::Debugger::SearchMatch>> matches =
- searchInTextByLinesImpl(m_session, it->second->source(m_isolate), query,
+ *results = std::make_unique<protocol::Array<protocol::Debugger::SearchMatch>>(
+ searchInTextByLinesImpl(m_session, it->second->source(0), query,
optionalCaseSensitive.fromMaybe(false),
- optionalIsRegex.fromMaybe(false));
- *results = protocol::Array<protocol::Debugger::SearchMatch>::create();
- for (size_t i = 0; i < matches.size(); ++i)
- (*results)->addItem(std::move(matches[i]));
- return Response::OK();
+ optionalIsRegex.fromMaybe(false)));
+ return Response::Success();
}
Response V8DebuggerAgentImpl::setScriptSource(
const String16& scriptId, const String16& newContent, Maybe<bool> dryRun,
Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames,
- Maybe<bool>* stackChanged, Maybe<StackTrace>* asyncStackTrace,
+ Maybe<bool>* stackChanged,
+ Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
+ Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId,
Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) {
- if (!enabled()) return Response::Error(kDebuggerNotEnabled);
+ if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
ScriptsMap::iterator it = m_scripts.find(scriptId);
if (it == m_scripts.end()) {
- return Response::Error("No script with given id found");
+ return Response::ServerError("No script with given id found");
}
- if (it->second->isModule()) {
- // TODO(kozyatinskiy): LiveEdit should support ES6 module
- return Response::Error("Editing module's script is not supported.");
+ 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);
- v8::HandleScope handles(m_isolate);
- v8::Local<v8::String> newSource = toV8String(m_isolate, newContent);
- bool compileError = false;
- Response response = m_debugger->setScriptSource(
- scriptId, newSource, dryRun.fromMaybe(false), optOutCompileError,
- &m_pausedCallFrames, stackChanged, &compileError);
- if (!response.isSuccess() || compileError) return response;
-
- it->second->setSource(newSource);
+ v8::debug::LiveEditResult result;
+ it->second->setSource(newContent, dryRun.fromMaybe(false), &result);
+ if (result.status != v8::debug::LiveEditResult::OK) {
+ *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();
+ } else {
+ *stackChanged = result.stack_changed;
+ }
std::unique_ptr<Array<CallFrame>> callFrames;
- response = currentCallFrames(&callFrames);
- if (!response.isSuccess()) return response;
+ Response response = currentCallFrames(&callFrames);
+ if (!response.IsSuccess()) return response;
*newCallFrames = std::move(callFrames);
*asyncStackTrace = currentAsyncStackTrace();
- return Response::OK();
+ *asyncStackTraceId = currentExternalStackTrace();
+ return Response::Success();
}
Response V8DebuggerAgentImpl::restartFrame(
const String16& callFrameId,
std::unique_ptr<Array<CallFrame>>* newCallFrames,
- Maybe<StackTrace>* asyncStackTrace) {
- if (!isPaused()) return Response::Error(kDebuggerNotPaused);
- InjectedScript::CallFrameScope scope(m_inspector, m_session->contextGroupId(),
- callFrameId);
+ Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
+ Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId) {
+ if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
+ InjectedScript::CallFrameScope scope(m_session, callFrameId);
Response response = scope.initialize();
- if (!response.isSuccess()) return response;
- if (scope.frameOrdinal() >= m_pausedCallFrames.size())
- return Response::Error("Could not find call frame with given id");
-
- v8::Local<v8::Value> resultValue;
- v8::Local<v8::Boolean> result;
- if (!m_pausedCallFrames[scope.frameOrdinal()]->restart().ToLocal(
- &resultValue) ||
- scope.tryCatch().HasCaught() ||
- !resultValue->ToBoolean(scope.context()).ToLocal(&result) ||
- !result->Value()) {
+ 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");
+ }
+ if (!it->Restart()) {
return Response::InternalError();
}
- JavaScriptCallFrames frames = m_debugger->currentCallFrames();
- m_pausedCallFrames.swap(frames);
-
response = currentCallFrames(newCallFrames);
- if (!response.isSuccess()) return response;
+ if (!response.IsSuccess()) return response;
*asyncStackTrace = currentAsyncStackTrace();
- return Response::OK();
+ *asyncStackTraceId = currentExternalStackTrace();
+ return Response::Success();
}
-Response V8DebuggerAgentImpl::getScriptSource(const String16& scriptId,
- String16* scriptSource) {
- if (!enabled()) return Response::Error(kDebuggerNotEnabled);
+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())
- return Response::Error("No script for id: " + scriptId);
- v8::HandleScope handles(m_isolate);
- *scriptSource = it->second->source(m_isolate);
- return Response::OK();
+ return Response::ServerError("No script for id: " + scriptId.utf8());
+ *scriptSource = it->second->source(0);
+ 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());
+ }
+ return Response::Success();
+}
+
+Response V8DebuggerAgentImpl::getWasmBytecode(const String16& scriptId,
+ protocol::Binary* bytecode) {
+ 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();
}
void V8DebuggerAgentImpl::pushBreakDetails(
@@ -606,78 +1089,93 @@
void V8DebuggerAgentImpl::schedulePauseOnNextStatement(
const String16& breakReason,
std::unique_ptr<protocol::DictionaryValue> data) {
- if (!enabled() || m_scheduledDebuggerStep == StepInto ||
- m_javaScriptPauseScheduled || isPaused() ||
- !m_debugger->breakpointsActivated())
- return;
- if (m_breakReason.empty()) m_debugger->setPauseOnNextStatement(true);
+ 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::schedulePauseOnNextStatementIfSteppingInto() {
- DCHECK(enabled());
- if (m_scheduledDebuggerStep != StepInto || m_javaScriptPauseScheduled ||
- isPaused())
- return;
- m_debugger->setPauseOnNextStatement(true);
-}
-
void V8DebuggerAgentImpl::cancelPauseOnNextStatement() {
- if (m_javaScriptPauseScheduled || isPaused()) return;
+ if (isPaused() || !acceptsPause(false) || !m_breakpointsActive) return;
+ if (m_breakReason.size() == 1) {
+ m_debugger->setPauseOnNextCall(false, m_session->contextGroupId());
+ }
popBreakDetails();
- if (m_breakReason.empty()) m_debugger->setPauseOnNextStatement(false);
}
Response V8DebuggerAgentImpl::pause() {
- if (!enabled()) return Response::Error(kDebuggerNotEnabled);
- if (m_javaScriptPauseScheduled || isPaused()) return Response::OK();
- clearBreakDetails();
- m_javaScriptPauseScheduled = true;
- m_scheduledDebuggerStep = NoStep;
- m_debugger->setPauseOnNextStatement(true);
- return Response::OK();
+ if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
+ if (isPaused()) return Response::Success();
+ if (m_debugger->canBreakProgram()) {
+ m_debugger->interruptAndBreak(m_session->contextGroupId());
+ } else {
+ if (m_breakReason.empty()) {
+ m_debugger->setPauseOnNextCall(true, m_session->contextGroupId());
+ }
+ pushBreakDetails(protocol::Debugger::Paused::ReasonEnum::Other, nullptr);
+ }
+ return Response::Success();
}
-Response V8DebuggerAgentImpl::resume() {
- if (!isPaused()) return Response::Error(kDebuggerNotPaused);
- m_scheduledDebuggerStep = NoStep;
+Response V8DebuggerAgentImpl::resume(Maybe<bool> terminateOnResume) {
+ if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
m_session->releaseObjectGroup(kBacktraceObjectGroup);
- m_debugger->continueProgram();
- return Response::OK();
+ m_debugger->continueProgram(m_session->contextGroupId(),
+ terminateOnResume.fromMaybe(false));
+ return Response::Success();
}
-Response V8DebuggerAgentImpl::stepOver() {
- if (!isPaused()) return Response::Error(kDebuggerNotPaused);
- // StepOver at function return point should fallback to StepInto.
- JavaScriptCallFrame* frame =
- !m_pausedCallFrames.empty() ? m_pausedCallFrames[0].get() : nullptr;
- if (frame && frame->isAtReturn()) return stepInto();
- m_scheduledDebuggerStep = StepOver;
+Response V8DebuggerAgentImpl::stepOver(
+ Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) {
+ if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
+
+ if (inSkipList.isJust()) {
+ const Response res = processSkipList(inSkipList.fromJust());
+ if (res.IsError()) return res;
+ } else {
+ m_skipList.clear();
+ }
+
m_session->releaseObjectGroup(kBacktraceObjectGroup);
- m_debugger->stepOverStatement();
- return Response::OK();
+ m_debugger->stepOverStatement(m_session->contextGroupId());
+ return Response::Success();
}
-Response V8DebuggerAgentImpl::stepInto() {
- if (!isPaused()) return Response::Error(kDebuggerNotPaused);
- m_scheduledDebuggerStep = StepInto;
+Response V8DebuggerAgentImpl::stepInto(
+ Maybe<bool> inBreakOnAsyncCall,
+ Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) {
+ if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
+
+ if (inSkipList.isJust()) {
+ const Response res = processSkipList(inSkipList.fromJust());
+ if (res.IsError()) return res;
+ } else {
+ m_skipList.clear();
+ }
+
m_session->releaseObjectGroup(kBacktraceObjectGroup);
- m_debugger->stepIntoStatement();
- return Response::OK();
+ m_debugger->stepIntoStatement(m_session->contextGroupId(),
+ inBreakOnAsyncCall.fromMaybe(false));
+ return Response::Success();
}
Response V8DebuggerAgentImpl::stepOut() {
- if (!isPaused()) return Response::Error(kDebuggerNotPaused);
- m_scheduledDebuggerStep = StepOut;
- m_recursionLevelForStepOut = 1;
+ if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
m_session->releaseObjectGroup(kBacktraceObjectGroup);
- m_debugger->stepOutOfFunction();
- return Response::OK();
+ 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::Error(kDebuggerNotEnabled);
+ if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
v8::debug::ExceptionBreakState pauseState;
if (stringPauseState == "none") {
pauseState = v8::debug::NoBreakOnException;
@@ -686,14 +1184,16 @@
} else if (stringPauseState == "uncaught") {
pauseState = v8::debug::BreakOnUncaughtException;
} else {
- return Response::Error("Unknown pause on exceptions mode: " +
- stringPauseState);
+ return Response::ServerError("Unknown pause on exceptions mode: " +
+ stringPauseState.utf8());
}
setPauseOnExceptionsImpl(pauseState);
- return Response::OK();
+ 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);
@@ -703,98 +1203,195 @@
const String16& callFrameId, const String16& expression,
Maybe<String16> objectGroup, Maybe<bool> includeCommandLineAPI,
Maybe<bool> silent, Maybe<bool> returnByValue, Maybe<bool> generatePreview,
- Maybe<bool> throwOnSideEffect, std::unique_ptr<RemoteObject>* result,
+ Maybe<bool> throwOnSideEffect, Maybe<double> timeout,
+ std::unique_ptr<RemoteObject>* result,
Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
- if (!isPaused()) return Response::Error(kDebuggerNotPaused);
- InjectedScript::CallFrameScope scope(m_inspector, m_session->contextGroupId(),
- callFrameId);
+ if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
+ InjectedScript::CallFrameScope scope(m_session, callFrameId);
Response response = scope.initialize();
- if (!response.isSuccess()) return response;
- if (scope.frameOrdinal() >= m_pausedCallFrames.size())
- return Response::Error("Could not find call frame with given id");
-
+ if (!response.IsSuccess()) return response;
if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
- v8::MaybeLocal<v8::Value> maybeResultValue =
- m_pausedCallFrames[scope.frameOrdinal()]->evaluate(
- toV8String(m_isolate, expression),
- throwOnSideEffect.fromMaybe(false));
+ 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.isJust()) {
+ response = evaluateScope.setTimeout(timeout.fromJust() / 1000.0);
+ if (!response.IsSuccess()) return response;
+ }
+ maybeResultValue = it->Evaluate(toV8String(m_isolate, expression),
+ throwOnSideEffect.fromMaybe(false));
+ }
+ // Re-initialize after running client's code, as it could have destroyed
+ // context or session.
+ response = scope.initialize();
+ if (!response.IsSuccess()) return response;
+ WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
+ : WrapMode::kNoPreview;
+ if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
+ return scope.injectedScript()->wrapEvaluateResult(
+ maybeResultValue, scope.tryCatch(), objectGroup.fromMaybe(""), mode,
+ result, exceptionDetails);
+}
+
+Response V8DebuggerAgentImpl::executeWasmEvaluator(
+ const String16& callFrameId, const protocol::Binary& evaluator,
+ Maybe<double> timeout,
+ std::unique_ptr<protocol::Runtime::RemoteObject>* result,
+ Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
+ if (!v8::debug::StackTraceIterator::SupportsWasmDebugEvaluate()) {
+ return Response::ServerError(
+ "--wasm-expose-debug-eval is required to execte evaluator modules");
+ }
+ if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
+ InjectedScript::CallFrameScope scope(m_session, callFrameId);
+ Response response = scope.initialize();
+ if (!response.IsSuccess()) return response;
+
+ int frameOrdinal = static_cast<int>(scope.frameOrdinal());
+ std::unique_ptr<v8::debug::StackTraceIterator> it =
+ v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal);
+ if (it->Done()) {
+ return Response::ServerError("Could not find call frame with given id");
+ }
+ if (!it->GetScript()->IsWasm()) {
+ return Response::ServerError(
+ "executeWasmEvaluator can only be called on WebAssembly frames");
+ }
+
+ v8::MaybeLocal<v8::Value> maybeResultValue;
+ {
+ V8InspectorImpl::EvaluateScope evaluateScope(scope);
+ if (timeout.isJust()) {
+ response = evaluateScope.setTimeout(timeout.fromJust() / 1000.0);
+ if (!response.IsSuccess()) return response;
+ }
+ v8::MaybeLocal<v8::String> eval_result =
+ it->EvaluateWasm({evaluator.data(), evaluator.size()}, frameOrdinal);
+ if (!eval_result.IsEmpty()) maybeResultValue = eval_result.ToLocalChecked();
+ }
// Re-initialize after running client's code, as it could have destroyed
// context or session.
response = scope.initialize();
- if (!response.isSuccess()) return response;
- return scope.injectedScript()->wrapEvaluateResult(
- maybeResultValue, scope.tryCatch(), objectGroup.fromMaybe(""),
- returnByValue.fromMaybe(false), generatePreview.fromMaybe(false), result,
- exceptionDetails);
+ if (!response.IsSuccess()) return response;
+
+ String16 object_group = "";
+ InjectedScript* injected_script = scope.injectedScript();
+ return injected_script->wrapEvaluateResult(maybeResultValue, scope.tryCatch(),
+ object_group, WrapMode::kNoPreview,
+ result, exceptionDetails);
}
Response V8DebuggerAgentImpl::setVariableValue(
int scopeNumber, const String16& variableName,
std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument,
const String16& callFrameId) {
- if (!enabled()) return Response::Error(kDebuggerNotEnabled);
- if (!isPaused()) return Response::Error(kDebuggerNotPaused);
- InjectedScript::CallFrameScope scope(m_inspector, m_session->contextGroupId(),
- 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;
+ if (!response.IsSuccess()) return response;
v8::Local<v8::Value> newValue;
response = scope.injectedScript()->resolveCallArgument(newValueArgument.get(),
&newValue);
- if (!response.isSuccess()) return response;
+ if (!response.IsSuccess()) return response;
- if (scope.frameOrdinal() >= m_pausedCallFrames.size())
- return Response::Error("Could not find call frame with given id");
- v8::MaybeLocal<v8::Value> result =
- m_pausedCallFrames[scope.frameOrdinal()]->setVariableValue(
- scopeNumber, toV8String(m_isolate, variableName), newValue);
- if (scope.tryCatch().HasCaught() || result.IsEmpty())
+ 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::OK();
+ }
+ 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()) return Response::Error(kDebuggerNotEnabled);
+ if (!enabled() && !m_session->runtimeAgent()->enabled()) {
+ return Response::ServerError(kDebuggerNotEnabled);
+ }
m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, depth);
m_debugger->setAsyncCallStackDepth(this, depth);
- return Response::OK();
+ return Response::Success();
}
Response V8DebuggerAgentImpl::setBlackboxPatterns(
std::unique_ptr<protocol::Array<String16>> patterns) {
- if (!patterns->length()) {
+ if (patterns->empty()) {
m_blackboxPattern = nullptr;
resetBlackboxedStateCache();
m_state->remove(DebuggerAgentState::blackboxPattern);
- return Response::OK();
+ return Response::Success();
}
String16Builder patternBuilder;
patternBuilder.append('(');
- for (size_t i = 0; i < patterns->length() - 1; ++i) {
- patternBuilder.append(patterns->get(i));
+ for (size_t i = 0; i < patterns->size() - 1; ++i) {
+ patternBuilder.append((*patterns)[i]);
patternBuilder.append("|");
}
- patternBuilder.append(patterns->get(patterns->length() - 1));
+ patternBuilder.append(patterns->back());
patternBuilder.append(')');
String16 pattern = patternBuilder.toString();
Response response = setBlackboxPattern(pattern);
- if (!response.isSuccess()) return response;
+ if (!response.IsSuccess()) return response;
resetBlackboxedStateCache();
m_state->setString(DebuggerAgentState::blackboxPattern, pattern);
- return Response::OK();
+ 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::Error("Pattern parser error: " + regex->errorMessage());
+ return Response::ServerError("Pattern parser error: " +
+ regex->errorMessage().utf8());
m_blackboxPattern = std::move(regex);
- return Response::OK();
+ return Response::Success();
}
void V8DebuggerAgentImpl::resetBlackboxedStateCache() {
@@ -809,193 +1406,196 @@
inPositions) {
auto it = m_scripts.find(scriptId);
if (it == m_scripts.end())
- return Response::Error("No script with passed id.");
+ return Response::ServerError("No script with passed id.");
- if (!inPositions->length()) {
+ if (inPositions->empty()) {
m_blackboxedPositions.erase(scriptId);
it->second->resetBlackboxedStateCache();
- return Response::OK();
+ return Response::Success();
}
std::vector<std::pair<int, int>> positions;
- positions.reserve(inPositions->length());
- for (size_t i = 0; i < inPositions->length(); ++i) {
- protocol::Debugger::ScriptPosition* position = inPositions->get(i);
- if (position->getLineNumber() < 0)
- return Response::Error("Position missing 'line' or 'line' < 0.");
- if (position->getColumnNumber() < 0)
- return Response::Error("Position missing 'column' or 'column' < 0.");
+ 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()));
}
-
- 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::Error(
- "Input positions array is not sorted or contains duplicate values.");
- }
+ Response res = isValidRangeOfPositions(positions);
+ if (res.IsError()) return res;
m_blackboxedPositions[scriptId] = positions;
it->second->resetBlackboxedStateCache();
- return Response::OK();
-}
-
-void V8DebuggerAgentImpl::willExecuteScript(int scriptId) {
- changeJavaScriptRecursionLevel(+1);
- if (m_scheduledDebuggerStep != StepInto) return;
- schedulePauseOnNextStatementIfSteppingInto();
-}
-
-void V8DebuggerAgentImpl::didExecuteScript() {
- changeJavaScriptRecursionLevel(-1);
-}
-
-void V8DebuggerAgentImpl::changeJavaScriptRecursionLevel(int step) {
- if (m_javaScriptPauseScheduled && !m_skipAllPauses && !isPaused()) {
- // Do not ever loose user's pause request until we have actually paused.
- m_debugger->setPauseOnNextStatement(true);
- }
- if (m_scheduledDebuggerStep == StepOut) {
- m_recursionLevelForStepOut += step;
- if (!m_recursionLevelForStepOut) {
- // When StepOut crosses a task boundary (i.e. js -> c++) from where it was
- // requested,
- // switch stepping to step into a next JS task, as if we exited to a
- // blackboxed framework.
- m_scheduledDebuggerStep = StepInto;
- }
- }
+ return Response::Success();
}
Response V8DebuggerAgentImpl::currentCallFrames(
std::unique_ptr<Array<CallFrame>>* result) {
if (!isPaused()) {
- *result = Array<CallFrame>::create();
- return Response::OK();
+ *result = std::make_unique<Array<CallFrame>>();
+ return Response::Success();
}
v8::HandleScope handles(m_isolate);
- v8::Local<v8::Context> debuggerContext =
- v8::debug::GetDebugContext(m_isolate);
- v8::Context::Scope contextScope(debuggerContext);
-
- v8::Local<v8::Array> objects = v8::Array::New(m_isolate);
-
- for (size_t frameOrdinal = 0; frameOrdinal < m_pausedCallFrames.size();
- ++frameOrdinal) {
- const std::unique_ptr<JavaScriptCallFrame>& currentCallFrame =
- m_pausedCallFrames[frameOrdinal];
-
- v8::Local<v8::Object> details;
- if (!currentCallFrame->details().ToLocal(&details))
- return Response::InternalError();
-
- int contextId = currentCallFrame->contextId();
-
+ *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);
- String16 callFrameId =
- RemoteCallFrameId::serialize(contextId, static_cast<int>(frameOrdinal));
- if (!details
- ->Set(debuggerContext,
- toV8StringInternalized(m_isolate, "callFrameId"),
- toV8String(m_isolate, callFrameId))
- .FromMaybe(false)) {
- return Response::InternalError();
- }
+ 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> scopeChain;
- if (!details
- ->Get(debuggerContext,
- toV8StringInternalized(m_isolate, "scopeChain"))
- .ToLocal(&scopeChain) ||
- !scopeChain->IsArray()) {
- return Response::InternalError();
+ v8::Local<v8::Value> receiver;
+ if (iterator->GetReceiver().ToLocal(&receiver)) {
+ res =
+ injectedScript->wrapObject(receiver, kBacktraceObjectGroup,
+ WrapMode::kNoPreview, &protocolReceiver);
+ if (!res.IsSuccess()) return res;
}
- v8::Local<v8::Array> scopeChainArray = scopeChain.As<v8::Array>();
- Response response = injectedScript->wrapPropertyInArray(
- scopeChainArray, toV8StringInternalized(m_isolate, "object"),
- kBacktraceObjectGroup);
- if (!response.isSuccess()) return response;
- response = injectedScript->wrapObjectProperty(
- details, toV8StringInternalized(m_isolate, "this"),
- kBacktraceObjectGroup);
- if (!response.isSuccess()) return response;
- if (details
- ->Has(debuggerContext,
- toV8StringInternalized(m_isolate, "returnValue"))
- .FromMaybe(false)) {
- response = injectedScript->wrapObjectProperty(
- details, toV8StringInternalized(m_isolate, "returnValue"),
- kBacktraceObjectGroup);
- if (!response.isSuccess()) return response;
- }
- } else {
- if (!details
- ->Set(debuggerContext,
- toV8StringInternalized(m_isolate, "scopeChain"),
- v8::Array::New(m_isolate, 0))
- .FromMaybe(false)) {
- return Response::InternalError();
- }
- v8::Local<v8::Object> remoteObject = v8::Object::New(m_isolate);
- if (!remoteObject
- ->Set(debuggerContext, toV8StringInternalized(m_isolate, "type"),
- toV8StringInternalized(m_isolate, "undefined"))
- .FromMaybe(false)) {
- return Response::InternalError();
- }
- if (!details
- ->Set(debuggerContext, toV8StringInternalized(m_isolate, "this"),
- remoteObject)
- .FromMaybe(false)) {
- return Response::InternalError();
- }
- if (!details
- ->Delete(debuggerContext,
- toV8StringInternalized(m_isolate, "returnValue"))
- .FromMaybe(false)) {
- return Response::InternalError();
- }
+ }
+ if (!protocolReceiver) {
+ protocolReceiver = RemoteObject::create()
+ .setType(RemoteObject::TypeEnum::Undefined)
+ .build();
}
- if (!objects->Set(debuggerContext, static_cast<int>(frameOrdinal), details)
- .FromMaybe(false)) {
- return Response::InternalError();
+ 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();
+ String16 scriptId = String16::fromInteger(script->Id());
+ ScriptsMap::iterator scriptIterator =
+ m_scripts.find(location->getScriptId());
+ String16 url;
+ if (scriptIterator != m_scripts.end()) {
+ url = scriptIterator->second->sourceURL();
}
+
+ auto frame = CallFrame::create()
+ .setCallFrameId(callFrameId)
+ .setFunctionName(toProtocolString(
+ m_isolate, iterator->GetFunctionDebugName()))
+ .setLocation(std::move(location))
+ .setUrl(url)
+ .setScopeChain(std::move(scopes))
+ .setThis(std::move(protocolReceiver))
+ .build();
+
+ v8::Local<v8::Function> func = iterator->GetFunction();
+ if (!func.IsEmpty()) {
+ frame->setFunctionLocation(
+ protocol::Debugger::Location::create()
+ .setScriptId(String16::fromInteger(func->ScriptId()))
+ .setLineNumber(func->GetScriptLineNumber())
+ .setColumnNumber(func->GetScriptColumnNumber())
+ .build());
+ }
+
+ v8::Local<v8::Value> returnValue = iterator->GetReturnValue();
+ if (!returnValue.IsEmpty() && injectedScript) {
+ std::unique_ptr<RemoteObject> value;
+ res = injectedScript->wrapObject(returnValue, kBacktraceObjectGroup,
+ WrapMode::kNoPreview, &value);
+ if (!res.IsSuccess()) return res;
+ frame->setReturnValue(std::move(value));
+ }
+ (*result)->emplace_back(std::move(frame));
}
-
- std::unique_ptr<protocol::Value> protocolValue;
- Response response = toProtocolValue(debuggerContext, objects, &protocolValue);
- if (!response.isSuccess()) return response;
- protocol::ErrorSupport errorSupport;
- *result = Array<CallFrame>::fromValue(protocolValue.get(), &errorSupport);
- if (!*result) return Response::Error(errorSupport.errors());
- TranslateWasmStackTraceLocations(result->get(),
- m_debugger->wasmTranslation());
- return Response::OK();
+ return Response::Success();
}
-std::unique_ptr<StackTrace> V8DebuggerAgentImpl::currentAsyncStackTrace() {
- if (!isPaused()) return nullptr;
- V8StackTraceImpl* stackTrace = m_debugger->currentAsyncCallChain();
- return stackTrace ? stackTrace->buildInspectorObjectForTail(m_debugger)
- : nullptr;
+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);
}
-bool V8DebuggerAgentImpl::isPaused() const { return m_debugger->isPaused(); }
+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(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;
+ }
+}
+
+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;
+}
void V8DebuggerAgentImpl::didParseSource(
std::unique_ptr<V8DebuggerScript> script, bool success) {
v8::HandleScope handles(m_isolate);
- String16 scriptSource = script->source(m_isolate);
- if (!success) script->setSourceURL(findSourceURL(scriptSource, false));
- if (!success)
+ if (!success) {
+ DCHECK(!script->isSourceLoadedLazily());
+ 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);
@@ -1005,16 +1605,30 @@
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::StringUtil::parseJSON(inspected->auxData()));
+ protocol::Value::parseBinary(cbor.data(), cbor.size()));
}
bool isLiveEdit = script->isLiveEdit();
- bool hasSourceURL = script->hasSourceURL();
+ 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;
+ if (script->getLanguage() == V8DebuggerScript::Language::WebAssembly)
+ codeOffset = script->codeOffset();
+ std::unique_ptr<protocol::Debugger::DebugSymbols> debugSymbols =
+ getDebugSymbols(*script);
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());
@@ -1029,59 +1643,133 @@
Maybe<protocol::DictionaryValue> executionContextAuxDataParam(
std::move(executionContextAuxData));
const bool* isLiveEditParam = isLiveEdit ? &isLiveEdit : nullptr;
- const bool* hasSourceURLParam = hasSourceURL ? &hasSourceURL : nullptr;
+ const bool* hasSourceURLParam =
+ hasSourceURLComment ? &hasSourceURLComment : nullptr;
const bool* isModuleParam = isModule ? &isModule : nullptr;
- if (success)
- m_frontend.scriptParsed(
- scriptId, scriptURL, scriptRef->startLine(), scriptRef->startColumn(),
- scriptRef->endLine(), scriptRef->endColumn(), contextId,
- scriptRef->hash(m_isolate), std::move(executionContextAuxDataParam),
- isLiveEditParam, std::move(sourceMapURLParam), hasSourceURLParam,
- isModuleParam);
- else
+ std::unique_ptr<V8StackTraceImpl> stack =
+ V8StackTraceImpl::capture(m_inspector->debugger(), contextGroupId, 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(m_isolate), std::move(executionContextAuxDataParam),
- std::move(sourceMapURLParam), hasSourceURLParam, isModuleParam);
-
- if (scriptURL.isEmpty() || !success) return;
-
- protocol::DictionaryValue* breakpointsCookie =
- m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
- if (!breakpointsCookie) return;
-
- for (size_t i = 0; i < breakpointsCookie->size(); ++i) {
- auto cookie = breakpointsCookie->at(i);
- protocol::DictionaryValue* breakpointObject =
- protocol::DictionaryValue::cast(cookie.second);
- bool isRegex;
- breakpointObject->getBoolean(DebuggerAgentState::isRegex, &isRegex);
- String16 url;
- breakpointObject->getString(DebuggerAgentState::url, &url);
- if (!matches(m_inspector, scriptURL, url, isRegex)) continue;
- ScriptBreakpoint breakpoint;
- breakpoint.script_id = scriptId;
- breakpointObject->getInteger(DebuggerAgentState::lineNumber,
- &breakpoint.line_number);
- breakpointObject->getInteger(DebuggerAgentState::columnNumber,
- &breakpoint.column_number);
- breakpointObject->getString(DebuggerAgentState::condition,
- &breakpoint.condition);
- std::unique_ptr<protocol::Debugger::Location> location =
- resolveBreakpoint(cookie.first, breakpoint, UserBreakpointSource);
- if (location)
- m_frontend.breakpointResolved(cookie.first, std::move(location));
+ scriptRef->hash(), std::move(executionContextAuxDataParam),
+ std::move(sourceMapURLParam), hasSourceURLParam, isModuleParam,
+ scriptRef->length(), std::move(stackTrace), std::move(codeOffset),
+ std::move(scriptLanguage), embedderName);
+ return;
}
+
+ if (scriptRef->isSourceLoadedLazily()) {
+ m_frontend.scriptParsed(
+ scriptId, scriptURL, 0, 0, 0, 0, contextId, scriptRef->hash(),
+ std::move(executionContextAuxDataParam), isLiveEditParam,
+ std::move(sourceMapURLParam), hasSourceURLParam, isModuleParam, 0,
+ std::move(stackTrace), std::move(codeOffset), std::move(scriptLanguage),
+ std::move(debugSymbols), embedderName);
+ } else {
+ 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);
+
+ if (!matches(m_inspector, *scriptRef, type, selector)) continue;
+ String16 condition;
+ breakpointWithCondition.second->asString(&condition);
+ String16 hint;
+ bool hasHint =
+ breakpointHints && breakpointHints->getString(breakpointId, &hint);
+ if (hasHint) {
+ 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::didPause(int contextId,
- v8::Local<v8::Value> exception,
- const std::vector<String16>& hitBreakpoints,
- bool isPromiseRejection, bool isUncaught,
- bool isOOMBreak) {
- JavaScriptCallFrames frames = m_debugger->currentCallFrames();
- m_pausedCallFrames.swap(frames);
+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->setBreakpointOnRun(&debuggerBreakpointId)) return;
+ std::unique_ptr<protocol::DictionaryValue> data =
+ protocol::DictionaryValue::create();
+ data->setString("url", scriptRef->sourceURL());
+ data->setString("scriptId", scriptRef->scriptId());
+ if (!sourceMapURL.isEmpty()) data->setString("sourceMapURL", sourceMapURL);
+
+ m_breakpointsOnScriptRun[debuggerBreakpointId] = std::move(data);
+ m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId;
+ m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back(
+ debuggerBreakpointId);
+}
+
+void V8DebuggerAgentImpl::didPause(
+ int contextId, v8::Local<v8::Value> exception,
+ const std::vector<v8::debug::BreakpointId>& hitBreakpoints,
+ v8::debug::ExceptionType exceptionType, bool isUncaught, bool isOOMBreak,
+ bool isAssert) {
v8::HandleScope handles(m_isolate);
std::vector<BreakReason> hitReasons;
@@ -1089,47 +1777,55 @@
if (isOOMBreak) {
hitReasons.push_back(
std::make_pair(protocol::Debugger::Paused::ReasonEnum::OOM, nullptr));
+ } else if (isAssert) {
+ hitReasons.push_back(std::make_pair(
+ protocol::Debugger::Paused::ReasonEnum::Assert, nullptr));
} else if (!exception.IsEmpty()) {
InjectedScript* injectedScript = nullptr;
m_session->findInjectedScript(contextId, injectedScript);
if (injectedScript) {
String16 breakReason =
- isPromiseRejection
+ 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, false, false,
- &obj);
+ injectedScript->wrapObject(exception, kBacktraceObjectGroup,
+ WrapMode::kNoPreview, &obj);
std::unique_ptr<protocol::DictionaryValue> breakAuxData;
if (obj) {
- breakAuxData = obj->toValue();
+ std::vector<uint8_t> serialized;
+ obj->AppendSerialized(&serialized);
+ breakAuxData = protocol::DictionaryValue::cast(
+ protocol::Value::parseBinary(serialized.data(), serialized.size()));
breakAuxData->setBoolean("uncaught", isUncaught);
- } else {
- breakAuxData = nullptr;
}
hitReasons.push_back(
std::make_pair(breakReason, std::move(breakAuxData)));
}
}
- std::unique_ptr<Array<String16>> hitBreakpointIds = Array<String16>::create();
+ auto hitBreakpointIds = std::make_unique<Array<String16>>();
- bool hasDebugCommandBreakpointReason = false;
- for (const auto& point : hitBreakpoints) {
- DebugServerBreakpointToBreakpointIdAndSourceMap::iterator
- breakpointIterator = m_serverBreakpoints.find(point);
- if (breakpointIterator != m_serverBreakpoints.end()) {
- const String16& localId = breakpointIterator->second.first;
- hitBreakpointIds->addItem(localId);
-
- BreakpointSource source = breakpointIterator->second.second;
- if (!hasDebugCommandBreakpointReason &&
- source == DebugCommandBreakpointSource) {
- hasDebugCommandBreakpointReason = true;
- hitReasons.push_back(std::make_pair(
- protocol::Debugger::Paused::ReasonEnum::DebugCommand, nullptr));
- }
+ for (const auto& id : hitBreakpoints) {
+ auto it = m_breakpointsOnScriptRun.find(id);
+ if (it != m_breakpointsOnScriptRun.end()) {
+ hitReasons.push_back(std::make_pair(
+ protocol::Debugger::Paused::ReasonEnum::Instrumentation,
+ std::move(it->second)));
+ m_breakpointsOnScriptRun.erase(it);
+ continue;
}
+ 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) continue;
+ hitReasons.push_back(std::make_pair(
+ protocol::Debugger::Paused::ReasonEnum::DebugCommand, nullptr));
}
for (size_t i = 0; i < m_breakReason.size(); ++i) {
@@ -1160,72 +1856,125 @@
std::unique_ptr<Array<CallFrame>> protocolCallFrames;
Response response = currentCallFrames(&protocolCallFrames);
- if (!response.isSuccess()) protocolCallFrames = Array<CallFrame>::create();
+ if (!response.IsSuccess())
+ protocolCallFrames = std::make_unique<Array<CallFrame>>();
+
m_frontend.paused(std::move(protocolCallFrames), breakReason,
std::move(breakAuxData), std::move(hitBreakpointIds),
- currentAsyncStackTrace());
- m_scheduledDebuggerStep = NoStep;
- m_javaScriptPauseScheduled = false;
-
- if (!m_continueToLocationBreakpointId.isEmpty()) {
- m_debugger->removeBreakpoint(m_continueToLocationBreakpointId);
- m_continueToLocationBreakpointId = "";
- }
+ currentAsyncStackTrace(), currentExternalStackTrace());
}
void V8DebuggerAgentImpl::didContinue() {
- JavaScriptCallFrames emptyCallFrames;
- m_pausedCallFrames.swap(emptyCallFrames);
clearBreakDetails();
m_frontend.resumed();
+ m_frontend.flush();
}
void V8DebuggerAgentImpl::breakProgram(
const String16& breakReason,
std::unique_ptr<protocol::DictionaryValue> data) {
- if (!enabled() || !m_debugger->canBreakProgram() || m_skipAllPauses) return;
+ if (!enabled() || m_skipAllPauses || !m_debugger->canBreakProgram()) return;
std::vector<BreakReason> currentScheduledReason;
currentScheduledReason.swap(m_breakReason);
pushBreakDetails(breakReason, std::move(data));
- m_scheduledDebuggerStep = NoStep;
- m_debugger->breakProgram();
+
+ 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::breakProgramOnException(
- const String16& breakReason,
- std::unique_ptr<protocol::DictionaryValue> data) {
- if (!enabled() ||
- m_debugger->getPauseOnExceptionsState() == v8::debug::NoBreakOnException)
+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;
- breakProgram(breakReason, std::move(data));
+ }
+ setBreakpointImpl(breakpointId, function, condition);
}
-void V8DebuggerAgentImpl::setBreakpointAt(const String16& scriptId,
- int lineNumber, int columnNumber,
- BreakpointSource source,
- const String16& condition) {
- ScriptBreakpoint breakpoint(scriptId, lineNumber, columnNumber, condition);
- String16 breakpointId = generateBreakpointId(breakpoint, source);
- resolveBreakpoint(breakpointId, breakpoint, source);
-}
-
-void V8DebuggerAgentImpl::removeBreakpointAt(const String16& scriptId,
- int lineNumber, int columnNumber,
- BreakpointSource source) {
- removeBreakpointImpl(generateBreakpointId(
- ScriptBreakpoint(scriptId, lineNumber, columnNumber, String16()),
- source));
+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_scheduledDebuggerStep = NoStep;
m_blackboxedPositions.clear();
resetBlackboxedStateCache();
+ m_skipList.clear();
m_scripts.clear();
- m_breakpointIdToDebuggerBreakpointIds.clear();
+ m_cachedScriptIds.clear();
+ m_cachedScriptSize = 0;
}
+void V8DebuggerAgentImpl::ScriptCollected(const V8DebuggerScript* script) {
+ DCHECK_NE(m_scripts.find(script->scriptId()), m_scripts.end());
+ m_cachedScriptIds.push_back(script->scriptId());
+ // TODO(alph): Properly calculate size when sources are one-byte strings.
+ m_cachedScriptSize += script->length() * sizeof(uint16_t);
+
+ while (m_cachedScriptSize > m_maxScriptCacheSize) {
+ const String16& scriptId = m_cachedScriptIds.front();
+ size_t scriptSize = m_scripts[scriptId]->length() * sizeof(uint16_t);
+ DCHECK_GE(m_cachedScriptSize, scriptSize);
+ m_cachedScriptSize -= scriptSize;
+ m_scripts.erase(scriptId);
+ m_cachedScriptIds.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();
+}
} // namespace v8_inspector