[ Service ] Add line/column information to SourceLocation objects

Removes the need for requesting a full Script object, which can be
extremely large when including source code. This change will have a
relatively small impact on response sizes.

Related issues: https://github.com/dart-lang/sdk/issues/47215, https://github.com/flutter/devtools/issues/3382

TEST=pkg/vm_service tests updated

Change-Id: I27999c4b1da65d4f0c643fa8db1a019c0fd1d689
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/227640
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index e096691..dde0929 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 8.2.0-dev
+- Update to version `3.56` of the spec.
+- Added optional `line` and `column` properties to `SourceLocation`.
+
 ## 8.1.0
 - Update to version `3.55` of the spec.
 - Added `streamCpuSamplesWithUserTag` RPC.
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index e465dc5..1c8ad22 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=3.55
+version=3.56
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index 968a41f..7fda5c7 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -26,7 +26,7 @@
         HeapSnapshotObjectNoData,
         HeapSnapshotObjectNullData;
 
-const String vmServiceVersion = '3.55.0';
+const String vmServiceVersion = '3.56.0';
 
 /// @optional
 const String optional = 'optional';
@@ -7417,10 +7417,22 @@
   @optional
   int? endTokenPos;
 
+  /// The line associated with this location. Only provided for non-synthetic
+  /// token positions.
+  @optional
+  int? line;
+
+  /// The column associated with this location. Only provided for non-synthetic
+  /// token positions.
+  @optional
+  int? column;
+
   SourceLocation({
     required this.script,
     required this.tokenPos,
     this.endTokenPos,
+    this.line,
+    this.column,
   });
 
   SourceLocation._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
@@ -7428,6 +7440,8 @@
         createServiceObject(json['script'], const ['ScriptRef']) as ScriptRef?;
     tokenPos = json['tokenPos'] ?? -1;
     endTokenPos = json['endTokenPos'];
+    line = json['line'];
+    column = json['column'];
   }
 
   @override
@@ -7442,6 +7456,8 @@
       'tokenPos': tokenPos,
     });
     _setIfNotNull(json, 'endTokenPos', endTokenPos);
+    _setIfNotNull(json, 'line', line);
+    _setIfNotNull(json, 'column', column);
     return json;
   }
 
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index a01b273..99c5e18 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -3,7 +3,7 @@
   A library to communicate with a service implementing the Dart VM
   service protocol.
 
-version: 8.1.0
+version: 8.2.0-dev
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
 
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 bead75c..0fa939b 100644
--- a/pkg/vm_service/test/add_breakpoint_rpc_kernel_test.dart
+++ b/pkg/vm_service/test/add_breakpoint_rpc_kernel_test.dart
@@ -71,13 +71,17 @@
     expect(futureBpt1.resolved, isTrue);
     expect(script.getLineNumberFromTokenPos(futureBpt1.location!.tokenPos!),
         LINE_A);
+    expect(futureBpt1.location!.line, LINE_A);
     expect(
         script.getColumnNumberFromTokenPos(futureBpt1.location!.tokenPos!), 12);
+    expect(futureBpt1.location!.column, 12);
     expect(futureBpt2.resolved, isTrue);
     expect(script.getLineNumberFromTokenPos(futureBpt2.location!.tokenPos!),
         LINE_A);
+    expect(futureBpt2.location!.line, LINE_A);
     expect(
         script.getColumnNumberFromTokenPos(futureBpt2.location!.tokenPos!), 3);
+    expect(futureBpt2.location!.column, 3);
 
     // The first breakpoint hits before value is modified.
     InstanceRef result =
@@ -121,13 +125,19 @@
       print('$LINE_A:${col} -> ${resolvedLine}:${resolvedCol}');
       if (col <= 12) {
         expect(resolvedLine, LINE_A);
+        expect(bpt.location!.line, LINE_A);
         expect(resolvedCol, 3);
+        expect(bpt.location!.column, 3);
       } else if (col <= 36) {
         expect(resolvedLine, LINE_A);
+        expect(bpt.location!.line, LINE_A);
         expect(resolvedCol, 12);
+        expect(bpt.location!.column, 12);
       } else {
         expect(resolvedLine, LINE_B);
+        expect(bpt.location!.line, LINE_B);
         expect(resolvedCol, 12);
+        expect(bpt.location!.column, 12);
       }
       expect(
           (await service.removeBreakpoint(isolateId, bpt.id!)).type, 'Success');
diff --git a/pkg/vm_service/test/breakpoint_async_break_test.dart b/pkg/vm_service/test/breakpoint_async_break_test.dart
index d0c2443..b83e6ad 100644
--- a/pkg/vm_service/test/breakpoint_async_break_test.dart
+++ b/pkg/vm_service/test/breakpoint_async_break_test.dart
@@ -9,7 +9,8 @@
 import 'common/service_test_common.dart';
 import 'common/test_helper.dart';
 
-const int LINE = 18;
+const int LINE = 19;
+const int COL = 7;
 
 // Issue: https://github.com/dart-lang/sdk/issues/36622
 Future<void> testMain() async {
@@ -62,7 +63,10 @@
     expect(futureBpt.resolved, isTrue);
     expect(
         script.getLineNumberFromTokenPos(futureBpt.location!.tokenPos), LINE);
-    expect(script.getColumnNumberFromTokenPos(futureBpt.location!.tokenPos), 7);
+    expect(futureBpt.location!.line, LINE);
+    expect(
+        script.getColumnNumberFromTokenPos(futureBpt.location!.tokenPos), COL);
+    expect(futureBpt.location!.column, COL);
 
     // Remove the breakpoints.
     expect((await service.removeBreakpoint(isolateId, futureBpt.id!)).type,
diff --git a/pkg/vm_service/test/debugging_test.dart b/pkg/vm_service/test/debugging_test.dart
index 1ddee02..cf56e3e 100644
--- a/pkg/vm_service/test/debugging_test.dart
+++ b/pkg/vm_service/test/debugging_test.dart
@@ -91,6 +91,7 @@
     final SourceLocation location = bpt.location;
     expect(location.script!.id, script.id);
     expect(script.getLineNumberFromTokenPos(location.tokenPos!), 16);
+    expect(location.line, 16);
 
     isolate = await service.getIsolate(isolateId);
     expect(isolate.breakpoints!.length, 1);
@@ -103,13 +104,12 @@
     final isolateId = isolateRef.id!;
     final stack = await service.getStack(isolateId);
     expect(stack.frames!.length, greaterThanOrEqualTo(1));
-
-    Script script = (await service.getObject(
-        isolateId, stack.frames![0].location!.script!.id!)) as Script;
+    final location = stack.frames![0].location!;
+    Script script =
+        (await service.getObject(isolateId, location.script!.id!)) as Script;
     expect(script.uri, endsWith('debugging_test.dart'));
-    expect(
-        script.getLineNumberFromTokenPos(stack.frames![0].location!.tokenPos!),
-        16);
+    expect(script.getLineNumberFromTokenPos(location.tokenPos!), 16);
+    expect(location.line, 16);
   },
 
 // Stepping
@@ -136,13 +136,12 @@
     final isolateId = isolateRef.id!;
     final stack = await service.getStack(isolateId);
     expect(stack.frames!.length, greaterThanOrEqualTo(1));
-
-    final Script script = (await service.getObject(
-        isolateId, stack.frames![0].location!.script!.id!)) as Script;
+    final location = stack.frames![0].location!;
+    final Script script =
+        (await service.getObject(isolateId, location.script!.id!)) as Script;
     expect(script.uri, endsWith('debugging_test.dart'));
-    expect(
-        script.getLineNumberFromTokenPos(stack.frames![0].location!.tokenPos!),
-        17);
+    expect(script.getLineNumberFromTokenPos(location.tokenPos!), 17);
+    expect(location.line, 17);
   },
 // Remove breakpoint
   (VmService service, IsolateRef isolateRef) async {
@@ -214,6 +213,7 @@
         (await service.getObject(isolateId, bpt.location.script.id)) as Script;
     expect(script.uri, endsWith('debugging_test.dart'));
     expect(script.getLineNumberFromTokenPos(bpt.location.tokenPos), 14);
+    expect(bpt.location.line, 14);
 
     // Refresh isolate state.
     isolate = await service.getIsolate(isolateId);
@@ -226,13 +226,12 @@
     final isolateId = isolateRef.id!;
     final stack = await service.getStack(isolateId);
     expect(stack.frames!.length, greaterThanOrEqualTo(1));
-
-    final Script script = (await service.getObject(
-        isolateId, stack.frames![0].location!.script!.id!)) as Script;
+    final location = stack.frames![0].location!;
+    final Script script =
+        (await service.getObject(isolateId, location.script!.id!)) as Script;
     expect(script.uri, endsWith('debugging_test.dart'));
-    expect(
-        script.getLineNumberFromTokenPos(stack.frames![0].location!.tokenPos!),
-        14);
+    expect(script.getLineNumberFromTokenPos(location.tokenPos!), 14);
+    expect(location.line, 14);
   },
 ];
 
diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart
index 048b1e4..d9e8b70 100644
--- a/runtime/observatory/tests/service/get_version_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
     final result = await vm.invokeRpcNoUpgrade('getVersion', {});
     expect(result['type'], 'Version');
     expect(result['major'], 3);
-    expect(result['minor'], 55);
+    expect(result['minor'], 56);
     expect(result['_privateMajor'], 0);
     expect(result['_privateMinor'], 0);
   },
diff --git a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
index a3c7061..4e398bd 100644
--- a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
     final result = await vm.invokeRpcNoUpgrade('getVersion', {});
     expect(result['type'], equals('Version'));
     expect(result['major'], equals(3));
-    expect(result['minor'], equals(55));
+    expect(result['minor'], equals(56));
     expect(result['_privateMajor'], equals(0));
     expect(result['_privateMinor'], equals(0));
   },
diff --git a/runtime/vm/json_stream.cc b/runtime/vm/json_stream.cc
index 5194918..1d6e8ac 100644
--- a/runtime/vm/json_stream.cc
+++ b/runtime/vm/json_stream.cc
@@ -523,6 +523,13 @@
   if (end_token_pos.IsReal()) {
     location.AddProperty("endTokenPos", end_token_pos);
   }
+  intptr_t line = -1;
+  intptr_t column = -1;
+  // Add line and column information if token_pos is real.
+  if (script.GetTokenLocation(token_pos, &line, &column)) {
+    location.AddProperty("line", line);
+    location.AddProperty("column", column);
+  }
 }
 
 void JSONObject::AddLocation(const BreakpointLocation* bpt_loc) const {
diff --git a/runtime/vm/json_test.cc b/runtime/vm/json_test.cc
index ed619c2..672369c 100644
--- a/runtime/vm/json_test.cc
+++ b/runtime/vm/json_test.cc
@@ -182,7 +182,8 @@
       "Class\",\"fixedId\":true,\"id\":\"\",\"name\":\"Null\",\"location\":{"
       "\"type\":\"SourceLocation\",\"script\":{\"type\":\"@Script\","
       "\"fixedId\":true,\"id\":\"\",\"uri\":\"dart:core\\/null.dart\",\"_"
-      "kind\":\"kernel\"},\"tokenPos\":925,\"endTokenPos\":1165},\"library\":{"
+      "kind\":\"kernel\"},\"tokenPos\":925,\"endTokenPos\":1165,\"line\":23,"
+      "\"column\":1},\"library\":{"
       "\"type\":\"@Library\",\"fixedId\":true,\"id\":\"\",\"name\":\"dart."
       "core\",\"uri\":\"dart:core\"}},\"kind\":\"Null\",\"fixedId\":true,"
       "\"id\":\"\",\"valueAsString\":\"null\"},{\"object_key\":{\"type\":\"@"
@@ -190,7 +191,8 @@
       "\"fixedId\":true,\"id\":\"\",\"name\":\"Null\",\"location\":{\"type\":"
       "\"SourceLocation\",\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
       "\"id\":\"\",\"uri\":\"dart:core\\/null.dart\",\"_kind\":\"kernel\"},"
-      "\"tokenPos\":925,\"endTokenPos\":1165},\"library\":{\"type\":\"@"
+      "\"tokenPos\":925,\"endTokenPos\":1165,\"line\":23,\"column\":1},"
+      "\"library\":{\"type\":\"@"
       "Library\",\"fixedId\":true,\"id\":\"\",\"name\":\"dart.core\",\"uri\":"
       "\"dart:core\"}},\"kind\":\"Null\",\"fixedId\":true,\"id\":\"\","
       "\"valueAsString\":\"null\"}}]",
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index d6324d9..6bf1e40 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -15,7 +15,7 @@
 namespace dart {
 
 #define SERVICE_PROTOCOL_MAJOR_VERSION 3
-#define SERVICE_PROTOCOL_MINOR_VERSION 55
+#define SERVICE_PROTOCOL_MINOR_VERSION 56
 
 class Array;
 class EmbedderServiceHandler;
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 2c22e5f..64125ef 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.55
+# Dart VM Service Protocol 3.56
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.55_ of the Dart VM Service Protocol. This
+This document describes of _version 3.56_ of the Dart VM Service Protocol. This
 protocol is used to communicate with a running Dart Virtual Machine.
 
 To use the Service Protocol, start the VM with the *--observe* flag.
@@ -3878,6 +3878,14 @@
 
   // The last token of the location if this is a range.
   int endTokenPos [optional];
+
+  // The line associated with this location. Only provided for non-synthetic
+  // token positions.
+  int line [optional];
+
+  // The column associated with this location. Only provided for non-synthetic
+  // token positions.
+  int column [optional];
 }
 ```
 
@@ -4319,5 +4327,6 @@
 3.53 | Added `setIsolatePauseMode` RPC.
 3.54 | Added `CpuSamplesEvent`, updated `cpuSamples` property on `Event` to have type `CpuSamplesEvent`.
 3.55 | Added `streamCpuSamplesWithUserTag` RPC.
+3.56 | Added optional `line` and `column` properties to `SourceLocation`.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/source_report_test.cc b/runtime/vm/source_report_test.cc
index 2670ac6..b1af934 100644
--- a/runtime/vm/source_report_test.cc
+++ b/runtime/vm/source_report_test.cc
@@ -541,7 +541,8 @@
       "\"_intrinsic\":false,\"_native\":false,\"location\":{\"type\":"
       "\"SourceLocation\",\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
       "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"},"
-      "\"tokenPos\":0,\"endTokenPos\":11}},\"count\":1}]}]}],"
+      "\"tokenPos\":0,\"endTokenPos\":11,\"line\":1,\"column\":1}},\"count\":1}"
+      "]}]}],"
 
       // One script in the script table.
       "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
@@ -602,7 +603,8 @@
       "\"script\":{\"type\":\"@Script\","
       "\"fixedId\":true,\"id\":\"\","
       "\"uri\":\"file:\\/\\/\\/test-lib\","
-      "\"_kind\":\"kernel\"},\"tokenPos\":0,\"endTokenPos\":27},"
+      "\"_kind\":\"kernel\"},\"tokenPos\":0,\"endTokenPos\":27,\"line\":1,"
+      "\"column\":1},"
       "\"library\":{\"type\":\"@Library\",\"fixedId\":true,"
       "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"}},"
 
@@ -614,7 +616,8 @@
       "\"script\":{\"type\":\"@Script\","
       "\"fixedId\":true,\"id\":\"\","
       "\"uri\":\"file:\\/\\/\\/test-lib\","
-      "\"_kind\":\"kernel\"},\"tokenPos\":0,\"endTokenPos\":27},"
+      "\"_kind\":\"kernel\"},\"tokenPos\":0,\"endTokenPos\":27,\"line\":1,"
+      "\"column\":1},"
       "\"library\":{\"type\":\"@Library\",\"fixedId\":true,"
       "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"}"
       "},\"_kind\":\"RegularFunction\","
@@ -624,7 +627,8 @@
       "\"location\":{\"type\":\"SourceLocation\","
       "\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
       "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\","
-      "\"_kind\":\"kernel\"},\"tokenPos\":17,\"endTokenPos\":25}},"
+      "\"_kind\":\"kernel\"},\"tokenPos\":17,\"endTokenPos\":25,\"line\":2,"
+      "\"column\":3}},"
 
       "\"count\":2},"
 
@@ -635,7 +639,8 @@
       "\"script\":{\"type\":\"@Script\","
       "\"fixedId\":true,\"id\":\"\","
       "\"uri\":\"file:\\/\\/\\/test-lib\","
-      "\"_kind\":\"kernel\"},\"tokenPos\":29,\"endTokenPos\":58},"
+      "\"_kind\":\"kernel\"},\"tokenPos\":29,\"endTokenPos\":58,\"line\":4,"
+      "\"column\":1},"
       "\"library\":{\"type\":\"@Library\",\"fixedId\":true,"
       "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"}},"
 
@@ -647,7 +652,8 @@
       "\"script\":{\"type\":\"@Script\","
       "\"fixedId\":true,\"id\":\"\","
       "\"uri\":\"file:\\/\\/\\/test-lib\","
-      "\"_kind\":\"kernel\"},\"tokenPos\":29,\"endTokenPos\":58},"
+      "\"_kind\":\"kernel\"},\"tokenPos\":29,\"endTokenPos\":58,\"line\":4,"
+      "\"column\":1},"
       "\"library\":{\"type\":\"@Library\",\"fixedId\":true,"
       "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"}"
       "},\"_kind\":\"RegularFunction\","
@@ -657,7 +663,8 @@
       "\"location\":{\"type\":\"SourceLocation\","
       "\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
       "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\","
-      "\"_kind\":\"kernel\"},\"tokenPos\":48,\"endTokenPos\":56}},"
+      "\"_kind\":\"kernel\"},\"tokenPos\":48,\"endTokenPos\":56,\"line\":5,"
+      "\"column\":3}},"
 
       "\"count\":1}]}]}],"
 
@@ -713,7 +720,8 @@
       "intrinsic\":false,\"_native\":false,\"location\":{\"type\":"
       "\"SourceLocation\",\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
       "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"},"
-      "\"tokenPos\":0,\"endTokenPos\":11}},\"count\":1}]}],\"coverage\":{"
+      "\"tokenPos\":0,\"endTokenPos\":11,\"line\":1,\"column\":1}},\"count\":1}"
+      "]}],\"coverage\":{"
       "\"hits\":[26,37],\"misses\":[]}}],"
 
       // One script in the script table.