[VM/Debugger] Mark the `=` tokens within assignment statements as locations where breakpoints can be set

TEST=pkg/vm_service/test/break_on_equals_signs_of_assignments_test.dart

Fixes: https://github.com/dart-lang/sdk/issues/56932
Change-Id: Id5ed23c645b614586e4bf769dbbea9704035746b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/398680
Commit-Queue: Derek Xu <derekx@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/vm_service/test/async_star_step_out_test.dart b/pkg/vm_service/test/async_star_step_out_test.dart
index 4f6e3d5..879599f 100644
--- a/pkg/vm_service/test/async_star_step_out_test.dart
+++ b/pkg/vm_service/test/async_star_step_out_test.dart
@@ -77,7 +77,7 @@
   stepOut, // step out of generator.
 
   hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_H), // await for.
+  stoppedAtLineColumn(line: LINE_H, column: 46), // on '{'
   stepInto,
 
   hasStoppedAtBreakpoint, // debugger().
@@ -95,7 +95,7 @@
   stepOut, // step out of generator.
 
   hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_H), // await for.
+  stoppedAtLineColumn(line: LINE_H, column: 46), // on '{'
   stepInto,
 
   hasStoppedAtBreakpoint, // debugger().
diff --git a/pkg/vm_service/test/break_on_equals_signs_of_assignments_test.dart b/pkg/vm_service/test/break_on_equals_signs_of_assignments_test.dart
new file mode 100644
index 0000000..5bfafe4
--- /dev/null
+++ b/pkg/vm_service/test/break_on_equals_signs_of_assignments_test.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2024, 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';
+
+const String shortFile = 'break_on_equals_signs_of_assignments_test.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 38;
+const LINE_B = 40;
+const LINE_C = 42;
+const LINE_D = 44;
+const LINE_E = 46;
+const LINE_F = 48;
+const LINE_G = 51;
+// AUTOGENERATED END
+
+class E {
+  int x = 0;
+}
+
+void testeeMain() {
+  // In https://dartbug.com/56932, it was brought up that requesting a
+  // breakpoint on line A below used to result in a breakpoint getting set on
+  // the next line, but after we made some changes to breakpoint resolution
+  // (https://github.com/dart-lang/sdk/commit/cdacfd80f1f5e2940056dbc32e9a8faaf1928db1),
+  // requesting a breakpoint line A resulted in no breakpoint getting set. After
+  // having a discussion on that issue, we decided to mark the `=` tokens within
+  // assignment statements as locations where breakpoints can be set, and this
+  // test ensures that the marking is happening correctly.
+  var a = // LINE_A
+      123;
+  late var b = // LINE_B
+      123;
+  final c = // LINE_C
+      123;
+  late final d = // LINE_D
+      123;
+  a = // LINE_E
+      456;
+  b = // LINE_F
+      456;
+  final e = E();
+  e.x = // LINE_G
+      123;
+
+  print(a);
+  print(b);
+  print(c);
+  print(d);
+  print(e);
+}
+
+List<String> stops = [];
+
+const List<String> expected = [
+  '$shortFile:$LINE_A:9', // on '='
+  '$shortFile:$LINE_B:14', // on '='
+  '$shortFile:$LINE_C:11', // on '='
+  '$shortFile:$LINE_D:16', // on '='
+  // Lines E, F and G contain assignment expressions instead of assignment
+  // statements, so breakpoints get resolved on the LHS of them instead of on
+  // the '=' tokens.
+  '$shortFile:$LINE_E:3', // on 'a'
+  '$shortFile:$LINE_F:3', // on 'b'
+  '$shortFile:$LINE_G:5', // on 'x'
+];
+
+final tests = <IsolateTest>[
+  hasPausedAtStart,
+  setBreakpointAtLine(LINE_A),
+  setBreakpointAtLine(LINE_B),
+  setBreakpointAtLine(LINE_C),
+  setBreakpointAtLine(LINE_D),
+  setBreakpointAtLine(LINE_E),
+  setBreakpointAtLine(LINE_F),
+  setBreakpointAtLine(LINE_G),
+  resumeProgramRecordingStops(stops, false),
+  checkRecordedStops(stops, expected),
+];
+
+Future<void> main([args = const <String>[]]) => runIsolateTests(
+      args,
+      tests,
+      shortFile,
+      testeeConcurrent: testeeMain,
+      pauseOnStart: true,
+      pauseOnExit: true,
+    );
diff --git a/pkg/vm_service/test/breakpoints_ignore_late_initialization_error_instructions_test.dart b/pkg/vm_service/test/breakpoints_ignore_late_initialization_error_instructions_test.dart
index 1310b49..c22990d 100644
--- a/pkg/vm_service/test/breakpoints_ignore_late_initialization_error_instructions_test.dart
+++ b/pkg/vm_service/test/breakpoints_ignore_late_initialization_error_instructions_test.dart
@@ -16,9 +16,9 @@
 //
 // dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
 //
-const LINE_A = 33;
-const LINE_B = 42;
-const LINE_C = 51;
+const LINE_A = 34;
+const LINE_B = 44;
+const LINE_C = 53;
 // AUTOGENERATED END
 
 int getThree() => 3;
@@ -26,28 +26,30 @@
 void testeeMain() {
   // ignore: prefer_final_locals
   late int x = 1;
-  // When a late variable is read, the VM performs checks to ensure that the
-  // variable has been initialized. When a breakpoint is set on the following
-  // line, it should resolve to the [toDouble] call instruction, not to an
-  // instruction part of the late variable initialization checks.
-  final double xd = x.toDouble(); // LINE_A
+  final double xd =
+      // When a late variable is read, the VM performs checks to ensure that the
+      // variable has been initialized. When a breakpoint is set on the
+      // following line, it should resolve to the [toDouble] call instruction,
+      // not to an instruction part of the late variable initialization checks.
+      x.toDouble(); // LINE_A
   print(xd);
 
   late final int y = 2;
-  // When a late final variable is read, the VM performs checks to ensure that
-  // the variable has been assigned a value exactly once. When a breakpoint is
-  // set on the following line, it should resolve to the [toDouble] call
-  // instruction, not to an instruction part of the late variable assignment
-  // checks.
-  final double yd = y.toDouble(); // LINE_B
+  final double yd =
+      // When a late final variable is read, the VM performs checks to ensure
+      // that the variable has been assigned a value exactly once. When a
+      // breakpoint is set on the following line, it should resolve to the
+      // [toDouble] call instruction, not to an instruction part of the late
+      // variable assignment checks.
+      y.toDouble(); // LINE_B
   print(yd);
 
   late final int z;
   // When a late final variable is assigned a value, the VM performs checks to
   // ensure that the variable has not already been initialized. When a
-  // breakpoint is set on the following line, it should resolve to the
-  // [getThree] call instruction, not to an instruction part of the late
-  // variable initialization checks.
+  // breakpoint is set on the following line, it should resolve on the '='
+  // token, not to an instruction part of the late variable initialization
+  // checks.
   z = getThree(); // LINE_C
   print(z);
 }
@@ -55,8 +57,8 @@
 List<String> stops = [];
 
 const List<String> expected = [
-  '$shortFile:$LINE_A:23', // on 'toDouble'
-  '$shortFile:$LINE_B:23', // on 'toDouble'
+  '$shortFile:$LINE_A:9', // on 'toDouble'
+  '$shortFile:$LINE_B:9', // on 'toDouble'
   '$shortFile:$LINE_C:7', // on 'getThree'
 ];
 
diff --git a/pkg/vm_service/test/issue_27238_test.dart b/pkg/vm_service/test/issue_27238_test.dart
index c1ded19..11aa19a 100644
--- a/pkg/vm_service/test/issue_27238_test.dart
+++ b/pkg/vm_service/test/issue_27238_test.dart
@@ -15,24 +15,25 @@
 //
 // dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
 //
-const LINE_0 = 27;
-const LINE_A = 28;
-const LINE_B = 31;
+const LINE_0 = 28;
+const LINE_A = 29;
+const LINE_B = 30;
 const LINE_C = 32;
-const LINE_D = 34;
+const LINE_D = 33;
 const LINE_E = 35;
+const LINE_F = 36;
 // AUTOGENERATED END
 
 Future<void> testMain() async {
   debugger(); // LINE_0.
   final future1 = Future.value(); // LINE_A.
-  final future2 = Future.value();
+  final future2 = Future.value(); // LINE_B.
 
-  await future1; // LINE_B.
-  await future2; // LINE_C.
+  await future1; // LINE_C.
+  await future2; // LINE_D.
 
-  print('foo1'); // LINE_D.
-  print('foo2'); // LINE_E.
+  print('foo1'); // LINE_E.
+  print('foo2'); // LINE_F.
 }
 
 final tests = <IsolateTest>[
@@ -40,18 +41,28 @@
   stoppedAtLine(LINE_0),
   stepOver,
   hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_A),
+  stoppedAtLineColumn(line: LINE_A, column: 17), // on '='
   smartNext,
   hasStoppedAtBreakpoint,
+  stoppedAtLineColumn(line: LINE_A, column: 26), // on 'value'
   smartNext,
   hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_B),
+  stoppedAtLineColumn(line: LINE_B, column: 17), // on '='
+  smartNext,
+  hasStoppedAtBreakpoint,
+  stoppedAtLineColumn(line: LINE_B, column: 26), // on 'value'
   smartNext,
   hasStoppedAtBreakpoint,
   stoppedAtLine(LINE_C),
   smartNext,
   hasStoppedAtBreakpoint,
   stoppedAtLine(LINE_D),
+  smartNext,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_E),
+  smartNext,
+  hasStoppedAtBreakpoint,
+  stoppedAtLine(LINE_F),
   resumeIsolate,
 ];
 
diff --git a/runtime/observatory/tests/service/async_star_step_out_test.dart b/runtime/observatory/tests/service/async_star_step_out_test.dart
deleted file mode 100644
index 110f704..0000000
--- a/runtime/observatory/tests/service/async_star_step_out_test.dart
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) 2017, 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.
-//
-// VMOptions=--async-debugger --verbose-debug
-
-import 'dart:developer';
-import 'service_test_common.dart';
-import 'test_helper.dart';
-
-const LINE_A = 25;
-const LINE_B = 26;
-const LINE_C = 30;
-const LINE_D = 33;
-const LINE_E = 40;
-const LINE_F = 41;
-const LINE_G = 42;
-const LINE_H = 31;
-const LINE_I = 35;
-
-const LINE_0 = 31;
-const LINE_1 = 39;
-
-foobar() async* {
-  yield 1; // LINE_A.
-  yield 2; // LINE_B.
-}
-
-helper() async {
-  print('helper'); // LINE_C.
-  await for (var _ in foobar()) /* LINE_H */ {
-    debugger(); // LINE_0.
-    print('loop'); // LINE_D.
-  }
-  return null; // LINE_I.
-}
-
-testMain() {
-  debugger(); // LINE_1.
-  print('mmmmm'); // LINE_E.
-  helper(); // LINE_F.
-  print('z'); // LINE_G.
-}
-
-var tests = <IsolateTest>[
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_1),
-  stepOver, // debugger.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_E),
-  stepOver, // print.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_F),
-  stepInto,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_C),
-  stepOver, // print.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_H), // foobar().
-  stepInto,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_H), // await for.
-  stepInto,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_A),
-  stepOut, // step out of generator.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_H), // await for.
-  stepInto,
-
-  hasStoppedAtBreakpoint, // debugger().
-  stepInto,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_D), // print.
-  stepInto,
-
-  hasStoppedAtBreakpoint, // await for.
-  stepInto,
-
-  hasStoppedAtBreakpoint, // back in generator.
-  stoppedAtLine(LINE_B),
-  stepOut, // step out of generator.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_H), // await for.
-  stepInto,
-
-  hasStoppedAtBreakpoint, // debugger().
-  stepInto,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_D), // print.
-  stepInto,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_H), // await for.
-  stepInto,
-
-  hasStoppedAtBreakpoint,
-  stepOut, // step out of generator.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_I), // return null.
-];
-
-main(args) => runIsolateTestsSynchronous(args, tests,
-    testeeConcurrent: testMain, extraArgs: extraDebuggingArgs);
diff --git a/runtime/observatory/tests/service/async_step_out_test.dart b/runtime/observatory/tests/service/async_step_out_test.dart
deleted file mode 100644
index 1a728bb..0000000
--- a/runtime/observatory/tests/service/async_step_out_test.dart
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2017, 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.
-//
-// VMOptions=--async-debugger --verbose-debug
-
-import 'dart:developer';
-import 'service_test_common.dart';
-import 'test_helper.dart';
-
-// AUTOGENERATED START
-//
-// Update these constants by running:
-//
-// dart runtime/observatory/tests/service/update_line_numbers.dart <test.dart>
-//
-const int LINE_A = 29;
-const int LINE_B = 30;
-const int LINE_C = 31;
-const int LINE_G = 36;
-const int LINE_0 = 40;
-const int LINE_D = 41;
-const int LINE_E = 42;
-const int LINE_F = 43;
-const int LINE_H = 44;
-// AUTOGENERATED END
-
-Future<int> helper() async {
-  await null; // LINE_A.
-  print('line b'); // LINE_B.
-  print('line c'); // LINE_C.
-  return 0;
-}
-
-Future<void> testMain() async {
-  int process(int value) /* LINE_G */ {
-    return value + 1;
-  }
-
-  debugger(); // LINE_0.
-  print('line d'); // LINE_D.
-  await helper().then(process); // LINE_E.
-  final v = process(10); // LINE_F.
-  print('line h'); // LINE_H.
-}
-
-var tests = <IsolateTest>[
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_0),
-  stepOver, // debugger.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_D),
-  stepOver, // print.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_E),
-  stepInto,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_A),
-  asyncNext,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_B),
-  stepOver, // print.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_C),
-  stepOut, // out of helper to awaiter testMain.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_G),
-  stepOut, // out of helper to awaiter testMain.
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_F),
-  stepOver,
-
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_H),
-];
-
-main(args) => runIsolateTests(args, tests,
-    testeeConcurrent: testMain, extraArgs: extraDebuggingArgs);
diff --git a/runtime/observatory/tests/service/issue_27238_test.dart b/runtime/observatory/tests/service/issue_27238_test.dart
deleted file mode 100644
index 2b7d380..0000000
--- a/runtime/observatory/tests/service/issue_27238_test.dart
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2015, 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.
-// VMOptions=--verbose_debug
-
-import 'service_test_common.dart';
-import 'dart:async';
-import 'test_helper.dart';
-import 'dart:developer';
-
-const int LINE_A = 21;
-const int LINE_B = 24;
-const int LINE_C = 25;
-const int LINE_D = 27;
-const int LINE_E = 28;
-
-const int LINE_0 = 20;
-
-testMain() async {
-  debugger(); // LINE_0.
-  Future future1 = new Future.value(); // LINE_A.
-  Future future2 = new Future.value();
-
-  await future1; // LINE_B.
-  await future2; // LINE_C.
-
-  print('foo1'); // LINE_D.
-  print('foo2'); // LINE_E.
-}
-
-var tests = <IsolateTest>[
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_0),
-  stepOver,
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_A),
-  smartNext,
-  hasStoppedAtBreakpoint,
-  smartNext,
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_B),
-  smartNext,
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_C),
-  smartNext,
-  hasStoppedAtBreakpoint,
-  stoppedAtLine(LINE_D),
-  resumeIsolate,
-];
-
-main(args) => runIsolateTests(args, tests, testeeConcurrent: testMain);
diff --git a/runtime/vm/compiler/backend/memory_copy_test.cc b/runtime/vm/compiler/backend/memory_copy_test.cc
index 47b6aa7..014d646 100644
--- a/runtime/vm/compiler/backend/memory_copy_test.cc
+++ b/runtime/vm/compiler/backend/memory_copy_test.cc
@@ -209,8 +209,11 @@
 
       EXPECT(cursor.TryMatch({
           kMoveGlob,
+          kMatchAndMoveDebugStepCheck,
+          kMatchAndMoveDebugStepCheck,
           kMatchAndMoveRecordCoverage,
           {kMatchAndMoveStaticCall, &pointer},
+          kMatchAndMoveDebugStepCheck,
           kMatchAndMoveRecordCoverage,
           {kMatchAndMoveStaticCall, &pointer2},
           kMatchAndMoveRecordCoverage,
@@ -246,8 +249,11 @@
       ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
       EXPECT(cursor.TryMatch({
           kMoveGlob,
+          kMatchAndMoveDebugStepCheck,
+          kMatchAndMoveDebugStepCheck,
           kMatchAndMoveRecordCoverage,
           kMatchAndMoveStaticCall,
+          kMatchAndMoveDebugStepCheck,
           kMatchAndMoveRecordCoverage,
           kMatchAndMoveStaticCall,
           kMatchAndMoveRecordCoverage,
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index eb4cf85..6efe51a 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -5894,7 +5894,11 @@
                                            ? helper.equals_position_
                                            : helper.position_;
   if (position != nullptr) *position = helper.position_;
-  if (NeedsDebugStepCheck(stack(), debug_position) && !helper.IsHoisted()) {
+  if (debug_position.IsDebugPause() && !helper.IsHoisted() &&
+      // We always make it possible to add a breakpoint on the equals sign if it
+      // exists.
+      (helper.equals_position_.IsReal() ||
+       NeedsDebugStepCheck(stack(), debug_position))) {
     instructions = DebugStepCheck(debug_position) + instructions;
   }
   instructions += StoreLocal(helper.position_, variable);