[inspector] Fix mapping between location and offset.
We weren't really translating between location (line and column number)
and source position (character offset) consistently, especially when it
came to inline <script>s. There were also inconsistencies between what
Debugger.getPossibleBreakpoints and Debugger.setBreakpointByUrl would
do.
With this CL, we are now consistently operating under the following
assumptions:
(1) For inline <scripts>s with a //@ sourceURL annotation, we assume
that the line and column number that comes in via the protocol is
in terms of the source text of the script.
(2) For inline <script>s without said annotation, we assume that the
line and column numbers are in terms of the surrounding document.
This is finally aligned with how the DevTools front-end operates.
Fixed: chromium:1319828
Change-Id: I98c4ef04b34a97caf060ff4f32690b135edb6ee6
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3610622
Reviewed-by: Kim-Anh Tran <kimanh@chromium.org>
Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Kim-Anh Tran <kimanh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80292}
diff --git a/src/debug/debug-interface.cc b/src/debug/debug-interface.cc
index 90207ea..a2d7e42 100644
--- a/src/debug/debug-interface.cc
+++ b/src/debug/debug-interface.cc
@@ -535,16 +535,17 @@
#endif // V8_ENABLE_WEBASSEMBLY
i::Isolate* isolate = script->GetIsolate();
- i::Script::InitLineEnds(isolate, script);
- CHECK(script->line_ends().IsFixedArray());
- i::Handle<i::FixedArray> line_ends =
- i::Handle<i::FixedArray>::cast(i::handle(script->line_ends(), isolate));
- CHECK(line_ends->length());
- int start_offset = GetSourceOffset(start);
- int end_offset = end.IsEmpty()
- ? GetSmiValue(line_ends, line_ends->length() - 1) + 1
- : GetSourceOffset(end);
+ int start_offset, end_offset;
+ if (!GetSourceOffset(start, GetSourceOffsetMode::kClamp).To(&start_offset)) {
+ return false;
+ }
+ if (end.IsEmpty()) {
+ end_offset = std::numeric_limits<int>::max();
+ } else if (!GetSourceOffset(end, GetSourceOffsetMode::kClamp)
+ .To(&end_offset)) {
+ return false;
+ }
if (start_offset >= end_offset) return true;
std::vector<i::BreakLocation> v8_locations;
@@ -555,53 +556,72 @@
}
std::sort(v8_locations.begin(), v8_locations.end(), CompareBreakLocation);
- int current_line_end_index = 0;
for (const auto& v8_location : v8_locations) {
- int offset = v8_location.position();
- while (offset > GetSmiValue(line_ends, current_line_end_index)) {
- ++current_line_end_index;
- CHECK(current_line_end_index < line_ends->length());
- }
- int line_offset = 0;
-
- if (current_line_end_index > 0) {
- line_offset = GetSmiValue(line_ends, current_line_end_index - 1) + 1;
- }
- locations->emplace_back(
- current_line_end_index + script->line_offset(),
- offset - line_offset +
- (current_line_end_index == 0 ? script->column_offset() : 0),
- v8_location.type());
+ Location location = GetSourceLocation(v8_location.position());
+ locations->emplace_back(location.GetLineNumber(),
+ location.GetColumnNumber(), v8_location.type());
}
return true;
}
-int Script::GetSourceOffset(const Location& location) const {
+Maybe<int> Script::GetSourceOffset(const Location& location,
+ GetSourceOffsetMode mode) const {
i::Handle<i::Script> script = Utils::OpenHandle(this);
#if V8_ENABLE_WEBASSEMBLY
if (script->type() == i::Script::TYPE_WASM) {
DCHECK_EQ(0, location.GetLineNumber());
- return location.GetColumnNumber();
+ return Just(location.GetColumnNumber());
}
#endif // V8_ENABLE_WEBASSEMBLY
- int line = std::max(location.GetLineNumber() - script->line_offset(), 0);
+ int line = location.GetLineNumber();
int column = location.GetColumnNumber();
- if (line == 0) {
- column = std::max(0, column - script->column_offset());
+ if (!script->HasSourceURLComment()) {
+ // Line/column number for inline <script>s with sourceURL annotation
+ // are supposed to be related to the <script> tag, otherwise they
+ // are relative to the parent file. Keep this in sync with the logic
+ // in GetSourceLocation() below.
+ line -= script->line_offset();
+ if (line == 0) column -= script->column_offset();
}
i::Script::InitLineEnds(script->GetIsolate(), script);
- CHECK(script->line_ends().IsFixedArray());
i::Handle<i::FixedArray> line_ends = i::Handle<i::FixedArray>::cast(
i::handle(script->line_ends(), script->GetIsolate()));
- CHECK(line_ends->length());
- if (line >= line_ends->length())
- return GetSmiValue(line_ends, line_ends->length() - 1);
- int line_offset = GetSmiValue(line_ends, line);
- if (line == 0) return std::min(column, line_offset);
- int prev_line_offset = GetSmiValue(line_ends, line - 1);
- return std::min(prev_line_offset + column + 1, line_offset);
+ if (line < 0) {
+ if (mode == GetSourceOffsetMode::kClamp) {
+ return Just(0);
+ }
+ return Nothing<int>();
+ }
+ if (line >= line_ends->length()) {
+ if (mode == GetSourceOffsetMode::kClamp) {
+ return Just(GetSmiValue(line_ends, line_ends->length() - 1));
+ }
+ return Nothing<int>();
+ }
+ if (column < 0) {
+ if (mode != GetSourceOffsetMode::kClamp) {
+ return Nothing<int>();
+ }
+ column = 0;
+ }
+ int offset = column;
+ if (line > 0) {
+ int prev_line_end_offset = GetSmiValue(line_ends, line - 1);
+ offset += prev_line_end_offset + 1;
+ }
+ int line_end_offset = GetSmiValue(line_ends, line);
+ if (offset > line_end_offset) {
+ // Be permissive with columns that don't exist,
+ // as long as they are clearly within the range
+ // of the script.
+ if (line < line_ends->length() - 1 || mode == GetSourceOffsetMode::kClamp) {
+ return Just(line_end_offset);
+ }
+ return Nothing<int>();
+ }
+ return Just(offset);
}
Location Script::GetSourceLocation(int offset) const {
@@ -609,6 +629,10 @@
i::Script::PositionInfo info;
i::Script::GetPositionInfo(script, offset, &info, i::Script::WITH_OFFSET);
if (script->HasSourceURLComment()) {
+ // Line/column number for inline <script>s with sourceURL annotation
+ // are supposed to be related to the <script> tag, otherwise they
+ // are relative to the parent file. Keep this in sync with the logic
+ // in GetSourceOffset() above.
info.line -= script->line_offset();
if (info.line == 0) info.column -= script->column_offset();
}
@@ -627,7 +651,10 @@
BreakpointId* id) const {
i::Handle<i::Script> script = Utils::OpenHandle(this);
i::Isolate* isolate = script->GetIsolate();
- int offset = GetSourceOffset(*location);
+ int offset;
+ if (!GetSourceOffset(*location).To(&offset)) {
+ return false;
+ }
if (!isolate->debug()->SetBreakPointForScript(
script, Utils::OpenHandle(*condition), &offset, id)) {
return false;
diff --git a/src/debug/debug-interface.h b/src/debug/debug-interface.h
index 4f6e7c6..0bce930 100644
--- a/src/debug/debug-interface.h
+++ b/src/debug/debug-interface.h
@@ -215,7 +215,10 @@
const debug::Location& start, const debug::Location& end,
bool restrict_to_function,
std::vector<debug::BreakLocation>* locations) const;
- int GetSourceOffset(const debug::Location& location) const;
+ enum class GetSourceOffsetMode { kStrict, kClamp };
+ Maybe<int> GetSourceOffset(
+ const debug::Location& location,
+ GetSourceOffsetMode mode = GetSourceOffsetMode::kStrict) const;
v8::debug::Location GetSourceLocation(int offset) const;
bool SetScriptSource(v8::Local<v8::String> newSource, bool preview,
LiveEditResult* result) const;
diff --git a/src/inspector/v8-debugger-agent-impl.cc b/src/inspector/v8-debugger-agent-impl.cc
index 7f6ca65..112bd3c 100644
--- a/src/inspector/v8-debugger-agent-impl.cc
+++ b/src/inspector/v8-debugger-agent-impl.cc
@@ -180,8 +180,8 @@
String16 breakpointHint(const V8DebuggerScript& script, int lineNumber,
int columnNumber) {
- int offset = script.offset(lineNumber, columnNumber);
- if (offset == V8DebuggerScript::kNoOffset) return String16();
+ int offset;
+ if (!script.offset(lineNumber, columnNumber).To(&offset)) return String16();
String16 hint =
script.source(offset, kBreakpointHintMaxLength).stripWhiteSpace();
for (size_t i = 0; i < hint.length(); ++i) {
@@ -206,8 +206,8 @@
}
if (hint.isEmpty()) return;
- intptr_t sourceOffset = script.offset(*lineNumber, *columnNumber);
- if (sourceOffset == V8DebuggerScript::kNoOffset) return;
+ int sourceOffset;
+ if (!script.offset(*lineNumber, *columnNumber).To(&sourceOffset)) return;
intptr_t searchRegionOffset = std::max(
sourceOffset - kBreakpointHintMaxSearchOffset, static_cast<intptr_t>(0));
@@ -948,16 +948,6 @@
ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId);
if (scriptIterator == m_scripts.end()) return nullptr;
V8DebuggerScript* script = scriptIterator->second.get();
- if (lineNumber < script->startLine() || script->endLine() < lineNumber) {
- return nullptr;
- }
- if (lineNumber == script->startLine() &&
- columnNumber < script->startColumn()) {
- return nullptr;
- }
- if (lineNumber == script->endLine() && script->endColumn() < columnNumber) {
- return nullptr;
- }
v8::debug::BreakpointId debuggerBreakpointId;
v8::debug::Location location(lineNumber, columnNumber);
diff --git a/src/inspector/v8-debugger-script.cc b/src/inspector/v8-debugger-script.cc
index d115912..2b295e7 100644
--- a/src/inspector/v8-debugger-script.cc
+++ b/src/inspector/v8-debugger-script.cc
@@ -231,7 +231,7 @@
v8::debug::ResetBlackboxedStateCache(m_isolate, m_script.Get(m_isolate));
}
- int offset(int lineNumber, int columnNumber) const override {
+ v8::Maybe<int> offset(int lineNumber, int columnNumber) const override {
v8::HandleScope scope(m_isolate);
return m_script.Get(m_isolate)->GetSourceOffset(
v8::debug::Location(lineNumber, columnNumber));
diff --git a/src/inspector/v8-debugger-script.h b/src/inspector/v8-debugger-script.h
index eb80a14..e4823e2 100644
--- a/src/inspector/v8-debugger-script.h
+++ b/src/inspector/v8-debugger-script.h
@@ -90,8 +90,7 @@
std::vector<v8::debug::BreakLocation>* locations) = 0;
virtual void resetBlackboxedStateCache() = 0;
- static const int kNoOffset = -1;
- virtual int offset(int lineNumber, int columnNumber) const = 0;
+ virtual v8::Maybe<int> offset(int lineNumber, int columnNumber) const = 0;
virtual v8::debug::Location location(int offset) const = 0;
virtual bool setBreakpoint(const String16& condition,
diff --git a/test/cctest/test-debug.cc b/test/cctest/test-debug.cc
index 4c70451..aceaae5 100644
--- a/test/cctest/test-debug.cc
+++ b/test/cctest/test-debug.cc
@@ -4776,26 +4776,40 @@
CHECK_EQ(script->GetSourceLocation(start_d).GetColumnNumber(), 10);
// Test offsets.
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(1, 10)), start_a);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(2, 13)), start_b);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(3, 0)), start_b + 5);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(3, 2)), start_b + 7);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(4, 0)), start_b + 16);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(5, 12)), start_c);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(6, 0)), start_c + 6);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(7, 0)), start_c + 19);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(8, 0)), start_c + 35);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(9, 0)), start_c + 48);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(10, 0)), start_c + 64);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(11, 0)), start_c + 70);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(12, 10)), start_d);
- CHECK_EQ(script->GetSourceOffset(v8::debug::Location(13, 0)), start_d + 6);
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(1, 10)),
+ v8::Just(start_a));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(2, 13)),
+ v8::Just(start_b));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(3, 0)),
+ v8::Just(start_b + 5));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(3, 2)),
+ v8::Just(start_b + 7));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(4, 0)),
+ v8::Just(start_b + 16));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(5, 12)),
+ v8::Just(start_c));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(6, 0)),
+ v8::Just(start_c + 6));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(7, 0)),
+ v8::Just(start_c + 19));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(8, 0)),
+ v8::Just(start_c + 35));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(9, 0)),
+ v8::Just(start_c + 48));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(10, 0)),
+ v8::Just(start_c + 64));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(11, 0)),
+ v8::Just(start_c + 70));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(12, 10)),
+ v8::Just(start_d));
+ CHECK_EQ(script->GetSourceOffset(v8::debug::Location(13, 0)),
+ v8::Just(start_d + 6));
for (int i = 1; i <= num_lines_d; ++i) {
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(start_line_d + i, 0)),
- 6 + (i * line_length_d) + start_d);
+ v8::Just(6 + (i * line_length_d) + start_d));
}
CHECK_EQ(script->GetSourceOffset(v8::debug::Location(start_line_d + 17, 0)),
- start_d + 158);
+ v8::Nothing<int>());
// Make sure invalid inputs work properly.
const int last_position = static_cast<int>(strlen(source)) - 1;
diff --git a/test/inspector/debugger/breakpoints-expected.txt b/test/inspector/debugger/breakpoints-expected.txt
index 9d0bae5..c9b59e3 100644
--- a/test/inspector/debugger/breakpoints-expected.txt
+++ b/test/inspector/debugger/breakpoints-expected.txt
@@ -63,4 +63,4 @@
evaluating foo1(0):
hit expected breakpoint
evaluating foo2(0):
- not paused
+ hit expected breakpoint
diff --git a/test/inspector/debugger/breakpoints.js b/test/inspector/debugger/breakpoints.js
index ce9ab47..261f67f 100644
--- a/test/inspector/debugger/breakpoints.js
+++ b/test/inspector/debugger/breakpoints.js
@@ -93,7 +93,7 @@
}
//# sourceURL=test2.js`, 5);
await evaluate('foo1(0)', breakpointId);
- await evaluate('foo2(0)');
+ await evaluate('foo2(0)', breakpointId);
}
]);
diff --git a/test/inspector/debugger/regress-crbug-1319828-expected.txt b/test/inspector/debugger/regress-crbug-1319828-expected.txt
new file mode 100644
index 0000000..f1742e1
--- /dev/null
+++ b/test/inspector/debugger/regress-crbug-1319828-expected.txt
@@ -0,0 +1,18 @@
+Regression test for crbug.com/1319828
+
+Running test: testDebuggerGetPossibleBreakpoints
+
+function foo() {
+ |_|console.|C|log('Hello World!');
+|R|}
+
+
+Running test: testDebuggerSetBreakpointByUrl
+function foo() {
+ #console.log('Hello World!');
+}
+
+function foo() {
+ console.#log('Hello World!');
+}
+
diff --git a/test/inspector/debugger/regress-crbug-1319828.js b/test/inspector/debugger/regress-crbug-1319828.js
new file mode 100644
index 0000000..72bf5c8
--- /dev/null
+++ b/test/inspector/debugger/regress-crbug-1319828.js
@@ -0,0 +1,65 @@
+// Copyright 2022 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+let {session, contextGroup, Protocol} =
+ InspectorTest.start('Regression test for crbug.com/1319828');
+
+const source = `
+function foo() {
+ console.log('Hello World!');
+}
+//# sourceURL=foo.js`;
+contextGroup.addScript(source, 42, 3);
+session.setupScriptMap();
+
+InspectorTest.runAsyncTestSuite([
+ async function testDebuggerGetPossibleBreakpoints() {
+ const [{params: {scriptId}}] = await Promise.all([
+ Protocol.Debugger.onceScriptParsed(),
+ Protocol.Runtime.enable(),
+ Protocol.Debugger.enable(),
+ ]);
+ const {result: {locations}} =
+ await Protocol.Debugger.getPossibleBreakpoints({
+ start: {lineNumber: 1, columnNumber: 0, scriptId},
+ end: {lineNumber: 3, columnNumber: 1, scriptId},
+ });
+ await session.logBreakLocations(locations);
+ await Promise.all([
+ Protocol.Debugger.disable(),
+ Protocol.Runtime.disable(),
+ ]);
+ },
+
+ async function testDebuggerSetBreakpointByUrl() {
+ await Promise.all([
+ Protocol.Runtime.enable(),
+ Protocol.Debugger.enable(),
+ ]);
+ let {result: {breakpointId, locations}} =
+ await Protocol.Debugger.setBreakpointByUrl({
+ url: 'foo.js',
+ lineNumber: 2,
+ columnNumber: 0,
+ });
+ await Promise.all([
+ session.logSourceLocations(locations),
+ Protocol.Debugger.removeBreakpoint({breakpointId}),
+ ]);
+ ({result: {breakpointId, locations}} =
+ await Protocol.Debugger.setBreakpointByUrl({
+ url: 'foo.js',
+ lineNumber: 2,
+ columnNumber: 9,
+ }));
+ await Promise.all([
+ session.logSourceLocations(locations),
+ Protocol.Debugger.removeBreakpoint({breakpointId}),
+ ]);
+ await Promise.all([
+ Protocol.Debugger.disable(),
+ Protocol.Runtime.disable(),
+ ]);
+ },
+]);