[VM/Debugger] Fix behaviour of setting breakpoints
My older CL that aimed to fix inline breakpoints was not quite correct.
It introduced some desirable behaviour, but also some undesirable
behaviour like https://github.com/dart-lang/sdk/issues/54100. This CL
reverts most of the changes in
https://dart-review.googlesource.com/c/sdk/+/316440 and then builds on
top of that.
TEST=pkg/vm_service/test/column_breakpoint_test.dart,
pkg/vm_service/test/column_breakpoint_test.dart, other debugger tests
Fixes: https://github.com/dart-lang/sdk/issues/54100
Change-Id: I2f5fdd55070aea9412a0b37e83c930cd24382adf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/338040
Commit-Queue: Derek Xu <derekx@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/vm_service/test/add_breakpoint_rpc_kernel_test.dart b/pkg/vm_service/test/add_breakpoint_rpc_kernel_test.dart
index 1c11d2f..d9200af 100644
--- a/pkg/vm_service/test/add_breakpoint_rpc_kernel_test.dart
+++ b/pkg/vm_service/test/add_breakpoint_rpc_kernel_test.dart
@@ -144,12 +144,14 @@
bpt.location!.tokenPos!,
)!;
print('$LINE_A:$col -> $resolvedLine:$resolvedCol');
- if (col <= 12) {
+ if (col < 12) {
+ // The second 'incValue' begins at column 12.
expect(resolvedLine, LINE_A);
expect(bpt.location!.line, LINE_A);
expect(resolvedCol, 3);
expect(bpt.location!.column, 3);
} else if (col <= 36) {
+ // The newline character at the end of LINE_A is at column 36.
expect(resolvedLine, LINE_A);
expect(bpt.location!.line, LINE_A);
expect(resolvedCol, 12);
diff --git a/pkg/vm_service/test/break_on_function_declared_inside_map_test.dart b/pkg/vm_service/test/break_on_function_declared_inside_map_test.dart
new file mode 100644
index 0000000..68c55904
--- /dev/null
+++ b/pkg/vm_service/test/break_on_function_declared_inside_map_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 22;
+const LINE_B = 25;
+// AUTOGENERATED END
+
+const String shortFile = 'break_on_function_declared_inside_map_test.dart';
+
+void testMain() {
+ final funcs = {
+ /* LINE_A */ 'a': () {
+ print('a');
+ },
+ /* LINE_B */ 'b': () {
+ print('b');
+ },
+ };
+
+ funcs['a']!();
+ funcs['b']!();
+}
+
+final stops = <String>[];
+
+const expected = <String>[
+ '$shortFile:$LINE_A:23', // on '(' of '()'
+ '$shortFile:$LINE_B:23', // on '(' of '()'
+];
+
+final tests = <IsolateTest>[
+ hasPausedAtStart,
+ setBreakpointAtLineColumn(LINE_A, 24), // on ')' of '()'
+ setBreakpointAtLine(LINE_B),
+ resumeProgramRecordingStops(stops, false),
+ checkRecordedStops(stops, expected),
+];
+
+Future<void> main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'break_on_function_declared_inside_map_test.dart',
+ testeeConcurrent: testMain,
+ pauseOnStart: true,
+ pauseOnExit: true,
+ );
diff --git a/pkg/vm_service/test/column_breakpoint_test.dart b/pkg/vm_service/test/column_breakpoint_test.dart
index 3ceb6fe..ea1c187 100644
--- a/pkg/vm_service/test/column_breakpoint_test.dart
+++ b/pkg/vm_service/test/column_breakpoint_test.dart
@@ -5,27 +5,35 @@
import 'common/service_test_common.dart';
import 'common/test_helper.dart';
-void testMain() {
- final b = [1, 2].map((i) => i == 0).toList();
- print(b.length);
-}
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 21;
+const LINE_B = 22;
+// AUTOGENERATED END
-const int LINE = 9;
-const int COLUMN = 29;
const String shortFile = 'column_breakpoint_test.dart';
+void testMain() {
+ final b = [1, 2].map((i) => i == 0).toList(); // LINE_A
+ print(b.length); // LINE_B
+}
+
final stops = <String>[];
const expected = <String>[
- '$shortFile:${LINE + 0}:33', // on 'i == 0'
- '$shortFile:${LINE + 0}:33', // iterate twice
- '$shortFile:${LINE + 1}:11', //on 'b.length'
+ '$shortFile:$LINE_A:33', // on first '=' of 'i == 0'
+ '$shortFile:$LINE_A:33', // iterate twice
+ '$shortFile:$LINE_B:11', // on 'l' of 'b.length'
];
final tests = <IsolateTest>[
hasPausedAtStart,
- setBreakpointAtLineColumn(LINE, COLUMN), // on 'i == 0'
- setBreakpointAtLineColumn(LINE + 1, 9), // on 'b.length'
+ setBreakpointAtLineColumn(LINE_A, 34), // on second '=' of 'i == 0'
+ setBreakpointAtLineColumn(LINE_B, 13), // on 'n' of 'b.length'
resumeProgramRecordingStops(stops, false),
checkRecordedStops(stops, expected),
];
diff --git a/runtime/observatory/tests/service/add_breakpoint_rpc_kernel_test.dart b/runtime/observatory/tests/service/add_breakpoint_rpc_kernel_test.dart
index 96ae4b5..67fa8ba 100644
--- a/runtime/observatory/tests/service/add_breakpoint_rpc_kernel_test.dart
+++ b/runtime/observatory/tests/service/add_breakpoint_rpc_kernel_test.dart
@@ -88,7 +88,7 @@
int resolvedLine = await bpt.location!.getLine() as int;
int resolvedCol = await bpt.location!.getColumn() as int;
print('$LINE_A:${col} -> ${resolvedLine}:${resolvedCol}');
- if (col <= 12) {
+ if (col < 12) {
expect(resolvedLine, equals(LINE_A));
expect(resolvedCol, equals(3));
} else if (col <= 36) {
diff --git a/runtime/observatory/tests/service/column_breakpoint_test.dart b/runtime/observatory/tests/service/column_breakpoint_test.dart
index 8d78440..7f4da75 100644
--- a/runtime/observatory/tests/service/column_breakpoint_test.dart
+++ b/runtime/observatory/tests/service/column_breakpoint_test.dart
@@ -6,12 +6,12 @@
import 'service_test_common.dart';
void testMain() {
- var b = [1, 2].map((i) => i == 0).toList();
+ final b = [1, 2].map((i) => i == 0).toList();
print(b.length);
}
-const int LINE = 9;
-const int COLUMN = 29;
+const LINE_A = 9;
+const LINE_B = 10;
const String shortFile = "column_breakpoint_test.dart";
const String breakpointFile =
"package:observatory_test_package/column_breakpoint_test.dart";
@@ -19,15 +19,15 @@
List<String> stops = [];
const List<String> expected = [
- "$shortFile:${LINE + 0}:29", // on 'i == 0'
- "$shortFile:${LINE + 0}:29", // iterate twice
- "$shortFile:${LINE + 1}:11" //on 'b.length'
+ '$shortFile:$LINE_A:33', // on first '=' of 'i == 0'
+ '$shortFile:$LINE_A:33', // iterate twice
+ '$shortFile:$LINE_B:11', // on 'l' of 'b.length'
];
final tests = <IsolateTest>[
hasPausedAtStart,
- setBreakpointAtLineColumn(LINE, COLUMN), // on 'i == 0'
- setBreakpointAtLineColumn(LINE + 1, 9), // on 'b.length'
+ setBreakpointAtLineColumn(LINE_A, 34), // on second '=' of 'i == 0'
+ setBreakpointAtLineColumn(LINE_B, 13), // on 'n' of 'b.length'
resumeProgramRecordingStops(stops, false),
checkRecordedStops(stops, expected)
];
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index 779992b..d730e00 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -1895,7 +1895,12 @@
ClearCachedStackTraces();
}
-// Helper to refine the resolved token pos.
+// Helper that refines the resolved token pos.
+//
+// If |requested_column| is specified, then |exact_token_pos| must be the exact
+// position where the user requested a column breakpoint to be set. If
+// |requested_column| is |-1|, |exact_token_pos| must be
+// |TokenPosition::kNoSource|.
static void RefineBreakpointPos(const Script& script,
TokenPosition pos,
TokenPosition next_closest_token_position,
@@ -1907,6 +1912,10 @@
intptr_t* best_column,
intptr_t* best_line,
TokenPosition* best_token_pos) {
+ ASSERT(requested_column == -1 &&
+ exact_token_pos == TokenPosition::kNoSource ||
+ requested_column > -1 && exact_token_pos != TokenPosition::kNoSource);
+
intptr_t token_start_column = -1;
intptr_t token_line = -1;
if (requested_column >= 0) {
@@ -1914,14 +1923,19 @@
TokenPosition end_of_line_pos = TokenPosition::kNoSource;
script.GetTokenLocation(pos, &token_line, &token_start_column);
script.TokenRangeAtLine(token_line, &ignored, &end_of_line_pos);
- TokenPosition token_end_pos =
- TokenPosition::Min(next_closest_token_position, end_of_line_pos);
+
+ TokenPosition token_end_pos = TokenPosition::Min(
+ TokenPosition::Deserialize(next_closest_token_position.Pos() - 1),
+ end_of_line_pos);
if ((token_end_pos.IsReal() && exact_token_pos.IsReal() &&
(token_end_pos < exact_token_pos)) ||
(token_start_column > *best_column)) {
- // Prefer the token with the lowest column number compatible
- // with the requested column.
+ // We prefer the token with the lowest column number compatible with the
+ // requested column. The current token under consideration either ends
+ // before the requested column, and is thus incompatible with it, or
+ // has a higher column number than the best token we've found so far, so
+ // we reject the current token under consideration.
return;
}
}
@@ -1946,6 +1960,11 @@
// represents one line of the program text, but can represent a larger
// range on recursive calls.
//
+// If |requested_column| is specified, then |exact_token_pos| must be the exact
+// position where the user requested a column breakpoint to be set. If
+// |requested_column| is |-1|, |exact_token_pos| must be
+// |TokenPosition::kNoSource|.
+//
// The best fit is found in two passes.
//
// The first pass finds a candidate token which:
@@ -2001,6 +2020,9 @@
intptr_t requested_column,
TokenPosition exact_token_pos) {
ASSERT(!func.HasOptimizedCode());
+ ASSERT(requested_column == -1 &&
+ exact_token_pos == TokenPosition::kNoSource ||
+ requested_column > -1 && exact_token_pos != TokenPosition::kNoSource);
requested_token_pos =
TokenPosition::Max(requested_token_pos, func.token_pos());
@@ -2432,6 +2454,10 @@
return false;
}
+// If |requested_column| is specified, then |exact_token_pos| must be the exact
+// position where the user requested a column breakpoint to be set. If
+// |requested_column| is |-1|, |exact_token_pos| must be
+// |TokenPosition::kNoSource|.
BreakpointLocation* Debugger::SetCodeBreakpoints(
const GrowableHandlePtrArray<const Script>& scripts,
TokenPosition token_pos,
@@ -2440,6 +2466,10 @@
intptr_t requested_column,
TokenPosition exact_token_pos,
const GrowableObjectArray& functions) {
+ ASSERT(requested_column == -1 &&
+ exact_token_pos == TokenPosition::kNoSource ||
+ requested_column > -1 && exact_token_pos != TokenPosition::kNoSource);
+
Function& function = Function::Handle();
function ^= functions.At(0);
TokenPosition breakpoint_pos = ResolveBreakpointPos(
@@ -2526,14 +2556,6 @@
}
}
- TokenPosition exact_token_pos = token_pos;
-#if !defined(DART_PRECOMPILED_RUNTIME)
- if (token_pos != last_token_pos && requested_column >= 0) {
- exact_token_pos =
- FindExactTokenPosition(script, token_pos, requested_column);
- }
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
-
if (!func.IsNull()) {
// There may be more than one function object for a given function
// in source code. There may be implicit closure functions, and
@@ -2550,6 +2572,13 @@
// have already been compiled. We can resolve the breakpoint now.
// If requested_column is larger than zero, [token_pos, last_token_pos]
// governs one single line of code.
+ TokenPosition exact_token_pos = TokenPosition::kNoSource;
+#if !defined(DART_PRECOMPILED_RUNTIME)
+ if (token_pos != last_token_pos && requested_column >= 0) {
+ exact_token_pos =
+ FindExactTokenPosition(script, token_pos, requested_column);
+ }
+#endif // !defined(DART_PRECOMPILED_RUNTIME)
DeoptimizeWorld();
BreakpointLocation* loc =
SetCodeBreakpoints(scripts, token_pos, last_token_pos, requested_line,
@@ -2565,7 +2594,7 @@
if (FLAG_verbose_debug) {
intptr_t line_number = -1;
intptr_t column_number = -1;
- script.GetTokenLocation(exact_token_pos, &line_number, &column_number);
+ script.GetTokenLocation(token_pos, &line_number, &column_number);
if (func.IsNull()) {
OS::PrintErr(
"Registering pending breakpoint for "
@@ -2580,11 +2609,10 @@
}
const String& script_url = String::Handle(script.url());
BreakpointLocation* loc = GetBreakpointLocation(
- script_url, exact_token_pos, requested_line, requested_column);
+ script_url, token_pos, requested_line, requested_column);
if (loc == nullptr) {
- loc =
- new BreakpointLocation(this, scripts, exact_token_pos, exact_token_pos,
- requested_line, requested_column);
+ loc = new BreakpointLocation(this, scripts, token_pos, last_token_pos,
+ requested_line, requested_column);
RegisterBreakpointLocation(loc);
}
return loc;
@@ -2890,7 +2918,16 @@
location->debugger()->isolate());
// Ensure the location is resolved for the original function.
- location->EnsureIsResolved(function, location->token_pos());
+ TokenPosition exact_token_pos = TokenPosition::kNoSource;
+#if !defined(DART_PRECOMPILED_RUNTIME)
+ if (location->token_pos() != location->end_token_pos() &&
+ location->requested_column_number() >= 0) {
+ exact_token_pos = FindExactTokenPosition(
+ Script::Handle(location->script()), location->token_pos(),
+ location->requested_column_number());
+ }
+#endif // !defined(DART_PRECOMPILED_RUNTIME)
+ location->EnsureIsResolved(function, exact_token_pos);
if (FLAG_verbose_debug) {
Breakpoint* bpt = location->breakpoints();
while (bpt != nullptr) {