[VM] Read and report constant constructor coverage from dill

This CL makes use of the now included constant constructor coverage
in the dill file.

It works like this:
* When the CFE evaluates constants, every constant constructor
  invocation evaluated saves the reference to the constructor in the
  `Source` (from the Components uri to source table) for the callers
  Library.
* This data is loaded into the VM in a "raw" format.
* When a request for coverage comes in, the VM - on top of the normal
  coverage processing - goes through all scripts to find constant
  constructor coverage for the requested script and offset. Note that
  all scripts must be checked because library A can have evaluated a
  constructor from library B - so even if only coverage for library B
  was requested, library A has to be checked.
  For all constructors found the start and end position is reported as
  covered. Note that this does not mark any initializes and there are
  (at least currently) no good way of marking which initializes were
  evaluated (because it has to be stable across edits even when the
  `advanced invalidation feature` is enabled).
* Note that the reason for the coverage to work on references - as
  hinted above - is because we want it to be stable across hot reloads
  even if/when advanced invalidation is enabled. This means, that
  library A cannot record "positional coverage" for library B because
  library B might get (for instance) new comments that will make any old
  offsets invalid. By using references we always lookup in the current
  world and use the correct offsets.

https://github.com/dart-lang/sdk/issues/38934

TEST=Existing test suite, new tests for the new coverage added.

Change-Id: I925963d1a9b9907efe621c72deb7348fa3be5ae8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/171949
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/pkg/vm/test/incremental_compiler_test.dart b/pkg/vm/test/incremental_compiler_test.dart
index 52a98e6..6cd97fc 100644
--- a/pkg/vm/test/incremental_compiler_test.dart
+++ b/pkg/vm/test/incremental_compiler_test.dart
@@ -134,7 +134,9 @@
   /// If [getAllSources] is false it will ask specifically for report
   /// (and thus hits) for "lib1.dart" only.
   Future<Set<int>> collectAndCheckCoverageData(int port, bool getAllSources,
-      {bool resume: true}) async {
+      {bool resume: true,
+      bool onGetAllVerifyCount: true,
+      Set<int> coverageForLines}) async {
     RemoteVm remoteVm = new RemoteVm(port);
 
     // Wait for the script to have finished.
@@ -189,7 +191,7 @@
         }
         i++;
       }
-      if (getAllSources) {
+      if (getAllSources && onGetAllVerifyCount) {
         expect(scriptIdToIndex.length >= 2, isTrue);
       }
 
@@ -226,12 +228,16 @@
             }
           }
           for (int pos in coverage["misses"]) positions.add(pos);
-          for (int pos in range["possibleBreakpoints"]) positions.add(pos);
+          if (range["possibleBreakpoints"] != null) {
+            for (int pos in range["possibleBreakpoints"]) positions.add(pos);
+          }
           Map script = scriptIndexToScript[range["scriptIndex"]];
           Set<int> knownPositions = new Set<int>();
+          Map<int, int> tokenPosToLine = {};
           if (script["tokenPosTable"] != null) {
             for (List tokenPosTableLine in script["tokenPosTable"]) {
               for (int i = 1; i < tokenPosTableLine.length; i += 2) {
+                tokenPosToLine[tokenPosTableLine[i]] = tokenPosTableLine[0];
                 knownPositions.add(tokenPosTableLine[i]);
               }
             }
@@ -244,6 +250,14 @@
                   "line and column.");
             }
           }
+
+          if (coverageForLines != null) {
+            for (int pos in coverage["hits"]) {
+              if (lib1scriptIndices.contains(range["scriptIndex"])) {
+                coverageForLines.add(tokenPosToLine[pos]);
+              }
+            }
+          }
         }
       }
     }
@@ -486,6 +500,243 @@
     });
   });
 
+  group('multiple kernels constant coverage', () {
+    Directory mytest;
+    File main;
+    File lib1;
+    int lineForUnnamedConstructor;
+    int lineForNamedConstructor;
+    Process vm;
+    setUpAll(() {
+      mytest = Directory.systemTemp.createTempSync('incremental');
+      main = new File('${mytest.path}/main.dart')..createSync();
+      main.writeAsStringSync("""
+        // This file - combined with the lib - should have coverage for both
+        // constructors of Foo.
+        import 'lib1.dart' as lib1;
+
+        void testFunction() {
+          const foo = lib1.Foo.named();
+          const foo2 = lib1.Foo.named();
+          if (!identical(foo, foo2)) throw "what?";
+        }
+
+        main() {
+          lib1.testFunction();
+          testFunction();
+          print("main");
+        }
+      """);
+      lib1 = new File('${mytest.path}/lib1.dart')..createSync();
+      lib1.writeAsStringSync("""
+        // Compiling this file should mark the default constructor - but not the
+        // named constructor - as having coverage.
+        class Foo {
+          final int x;
+          const Foo([int? x]) : this.x = x ?? 42;
+          const Foo.named([int? x]) : this.x = x ?? 42;
+        }
+
+        void testFunction() {
+          const foo = Foo();
+          const foo2 = Foo();
+          if (!identical(foo, foo2)) throw "what?";
+        }
+
+        main() {
+          testFunction();
+          print("lib1");
+        }
+      """);
+      lineForUnnamedConstructor = 5;
+      lineForNamedConstructor = 6;
+    });
+
+    tearDownAll(() {
+      try {
+        mytest.deleteSync(recursive: true);
+      } catch (_) {
+        // Ignore errors;
+      }
+      try {
+        vm.kill();
+      } catch (_) {
+        // Ignore errors;
+      }
+    });
+
+    Future<Set<int>> runAndGetLineCoverage(
+        File list, String expectStdoutContains) async {
+      vm = await Process.start(Platform.resolvedExecutable, <String>[
+        "--pause-isolates-on-exit",
+        "--enable-vm-service:0",
+        "--disable-service-auth-codes",
+        "--disable-dart-dev",
+        list.path
+      ]);
+
+      const kObservatoryListening = 'Observatory listening on ';
+      final RegExp observatoryPortRegExp =
+          new RegExp("Observatory listening on http://127.0.0.1:\([0-9]*\)");
+      int port;
+      final splitter = new LineSplitter();
+      Completer<String> portLineCompleter = new Completer<String>();
+      Set<int> coverageLines = {};
+      bool foundExpectedString = false;
+      vm.stdout
+          .transform(utf8.decoder)
+          .transform(splitter)
+          .listen((String s) async {
+        if (s == expectStdoutContains) {
+          foundExpectedString = true;
+        }
+        if (s.startsWith(kObservatoryListening)) {
+          expect(observatoryPortRegExp.hasMatch(s), isTrue);
+          final match = observatoryPortRegExp.firstMatch(s);
+          port = int.parse(match.group(1));
+          await collectAndCheckCoverageData(port, true,
+              onGetAllVerifyCount: false, coverageForLines: coverageLines);
+          if (!portLineCompleter.isCompleted) {
+            portLineCompleter.complete("done");
+          }
+        }
+        print("vm stdout: $s");
+      });
+      vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
+        print("vm stderr: $s");
+      });
+      await portLineCompleter.future;
+      print("Compiler terminated with ${await vm.exitCode} exit code");
+      expect(foundExpectedString, isTrue);
+      return coverageLines;
+    }
+
+    test('compile seperatly, check coverage', () async {
+      Directory dir = mytest.createTempSync();
+
+      // First compile lib, run and verify coverage (un-named constructor
+      // covered, but not the named constructor).
+      // Note that it's called 'lib1' to match with expectations from coverage
+      // collector helper in this file.
+      File libDill = File(p.join(dir.path, p.basename(lib1.path + ".dill")));
+      IncrementalCompiler compiler = new IncrementalCompiler(options, lib1.uri);
+      Component component = await compiler.compile();
+      expect(component.libraries.length, equals(1));
+      expect(component.libraries.single.fileUri, equals(lib1.uri));
+      IOSink sink = libDill.openWrite();
+      BinaryPrinter printer = new BinaryPrinter(sink);
+      printer.writeComponentFile(component);
+      await sink.flush();
+      await sink.close();
+      File list = new File(p.join(dir.path, 'dill.list'))..createSync();
+      list.writeAsStringSync("#@dill\n${libDill.path}\n");
+      Set<int> lineCoverage = await runAndGetLineCoverage(list, "lib1");
+      // Expect coverage for unnamed constructor but not for the named one.
+      expect(
+          lineCoverage.intersection(
+              {lineForUnnamedConstructor, lineForNamedConstructor}),
+          equals({lineForUnnamedConstructor}));
+
+      try {
+        vm.kill();
+      } catch (_) {
+        // Ignore errors;
+      }
+      // Accept the compile to not include the lib again.
+      compiler.accept();
+
+      // Then compile lib, run and verify coverage (un-named constructor
+      // covered, and the named constructor coveraged too).
+      File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
+      component = await compiler.compile(entryPoint: main.uri);
+      expect(component.libraries.length, equals(1));
+      expect(component.libraries.single.fileUri, equals(main.uri));
+      sink = mainDill.openWrite();
+      printer = new BinaryPrinter(sink);
+      printer.writeComponentFile(component);
+      await sink.flush();
+      await sink.close();
+      list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
+      lineCoverage = await runAndGetLineCoverage(list, "main");
+
+      // Expect coverage for both unnamed constructor and for the named one.
+      expect(
+          lineCoverage.intersection(
+              {lineForUnnamedConstructor, lineForNamedConstructor}),
+          equals({lineForUnnamedConstructor, lineForNamedConstructor}));
+
+      try {
+        vm.kill();
+      } catch (_) {
+        // Ignore errors;
+      }
+      // Accept the compile to not include the lib again.
+      compiler.accept();
+
+      // Finally, change lib to shift the constructors so the old line numbers
+      // doesn't match. Compile lib by itself, compile lib, run with the old
+      // main and verify coverage is still correct (both un-named constructor
+      // and named constructor (at new line numbers) are covered, and the old
+      // line numbers are not coverage.
+
+      lib1.writeAsStringSync("""
+        //
+        // Shift lines down by five
+        // lines so the original
+        // lines can't be coverred
+        //
+        class Foo {
+          final int x;
+          const Foo([int? x]) : this.x = x ?? 42;
+          const Foo.named([int? x]) : this.x = x ?? 42;
+        }
+
+        void testFunction() {
+          const foo = Foo();
+          const foo2 = Foo();
+          if (!identical(foo, foo2)) throw "what?";
+        }
+
+        main() {
+          testFunction();
+          print("lib1");
+        }
+      """);
+      int newLineForUnnamedConstructor = 8;
+      int newLineForNamedConstructor = 9;
+      compiler.invalidate(lib1.uri);
+      component = await compiler.compile(entryPoint: lib1.uri);
+      expect(component.libraries.length, equals(1));
+      expect(component.libraries.single.fileUri, equals(lib1.uri));
+      sink = libDill.openWrite();
+      printer = new BinaryPrinter(sink);
+      printer.writeComponentFile(component);
+      await sink.flush();
+      await sink.close();
+      list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
+      lineCoverage = await runAndGetLineCoverage(list, "main");
+
+      // Expect coverage for both unnamed constructor and for the named one on
+      // the new positions, but no coverage on the old positions.
+      expect(
+          lineCoverage.intersection({
+            lineForUnnamedConstructor,
+            lineForNamedConstructor,
+            newLineForUnnamedConstructor,
+            newLineForNamedConstructor
+          }),
+          equals({newLineForUnnamedConstructor, newLineForNamedConstructor}));
+
+      try {
+        vm.kill();
+      } catch (_) {
+        // Ignore errors;
+      }
+      // Accept the compile to not include the lib again.
+      compiler.accept();
+    });
+  });
+
   group('multiple kernels 2', () {
     Directory mytest;
     File main;
diff --git a/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart b/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart
new file mode 100644
index 0000000..b0f078d
--- /dev/null
+++ b/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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 "get_source_report_const_coverage_test.dart";
+
+void testFunction() {
+  const namedFoo = Foo.named3();
+  const namedFoo2 = Foo.named3();
+  const namedIdentical = identical(namedFoo, namedFoo2);
+  print("namedIdentical: $namedIdentical");
+}
diff --git a/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart b/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart
new file mode 100644
index 0000000..32cb035
--- /dev/null
+++ b/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, 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 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+import 'test_helper.dart';
+import 'service_test_common.dart';
+import 'dart:developer';
+
+import 'get_source_report_const_coverage_lib.dart' as lib;
+
+const String filename = "get_source_report_const_coverage_test";
+const Set<int> expectedLinesHit = {20, 22, 26};
+const Set<int> expectedLinesNotHit = {24};
+
+class Foo {
+  final int x;
+  // Expect this constructor to be coverage by coverage.
+  const Foo([int? x]) : this.x = x ?? 42;
+  // Expect this constructor to be coverage by coverage too.
+  const Foo.named1([int? x]) : this.x = x ?? 42;
+  // Expect this constructor to *NOT* be coverage by coverage.
+  const Foo.named2([int? x]) : this.x = x ?? 42;
+  // Expect this constructor to be coverage by coverage too (from lib).
+  const Foo.named3([int? x]) : this.x = x ?? 42;
+}
+
+void testFunction() {
+  const foo = Foo();
+  const foo2 = Foo();
+  const fooIdentical = identical(foo, foo2);
+  print(fooIdentical);
+
+  const namedFoo = Foo.named1();
+  const namedFoo2 = Foo.named1();
+  const namedIdentical = identical(namedFoo, namedFoo2);
+  print(fooIdentical);
+
+  debugger();
+
+  // That this is called after (or at all) is not relevent for the code
+  // coverage of constants.
+  lib.testFunction();
+
+  print("Done");
+}
+
+var tests = <IsolateTest>[
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    final stack = await isolate.getStack();
+
+    // Make sure we are in the right place.
+    expect(stack.type, equals('Stack'));
+    expect(stack['frames'].length, greaterThanOrEqualTo(1));
+    expect(stack['frames'][0].function.name, equals('testFunction'));
+
+    final List<Script> scripts = await isolate.getScripts();
+    Script? foundScript;
+    for (Script script in scripts) {
+      if (script.uri.contains(filename)) {
+        foundScript = script;
+        break;
+      }
+    }
+
+    Set<int> hits;
+    {
+      // Get report for everything; then collect for this library.
+      final Map<String, Object> params = {
+        'reports': ['Coverage'],
+      };
+      final coverage =
+          await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+      hits = getHitsFor(coverage, filename);
+      await foundScript!.load();
+      final Set<int> lines = {};
+      for (int hit in hits) {
+        // We expect every hit to be translatable to line
+        // (i.e. tokenToLine to return non-null).
+        lines.add(foundScript.tokenToLine(hit)!);
+      }
+      print("Token position hits: $hits --- line hits: $lines");
+      expect(lines.intersection(expectedLinesHit), equals(expectedLinesHit));
+      expect(lines.intersection(expectedLinesNotHit), isEmpty);
+    }
+    {
+      // Now get report for the this file only.
+      final Map<String, Object> params = {
+        'reports': ['Coverage'],
+        'scriptId': foundScript.id!
+      };
+      final coverage =
+          await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+      final Set<int> localHits = getHitsFor(coverage, filename);
+      expect(localHits.length, equals(hits.length));
+      expect(hits.toList()..sort(), equals(localHits.toList()..sort()));
+      print(localHits);
+    }
+  },
+];
+
+Set<int> getHitsFor(Map coverage, String uriContains) {
+  final List scripts = coverage["scripts"];
+  final Set<int> scriptIdsWanted = {};
+  for (int i = 0; i < scripts.length; i++) {
+    final Map script = scripts[i];
+    final String scriptUri = script["uri"];
+    if (scriptUri.contains(uriContains)) {
+      scriptIdsWanted.add(i);
+    }
+  }
+  final List ranges = coverage["ranges"];
+  final Set<int> hits = {};
+  for (int i = 0; i < ranges.length; i++) {
+    final Map range = ranges[i];
+    if (scriptIdsWanted.contains(range["scriptIndex"])) {
+      if (range["coverage"] != null) {
+        for (int hit in range["coverage"]["hits"]) {
+          hits.add(hit);
+        }
+      }
+    }
+  }
+  return hits;
+}
+
+main(args) => runIsolateTests(args, tests, testeeConcurrent: testFunction);
diff --git a/runtime/observatory/tests/service/get_source_report_test.dart b/runtime/observatory/tests/service/get_source_report_test.dart
index adea481..a1d5206 100644
--- a/runtime/observatory/tests/service/get_source_report_test.dart
+++ b/runtime/observatory/tests/service/get_source_report_test.dart
@@ -91,7 +91,7 @@
     final numRanges = coverage['ranges'].length;
     expect(coverage['type'], equals('SourceReport'));
 
-    expect(numRanges, equals(10));
+    expect(numRanges, equals(11));
     expect(coverage['ranges'][0], equals(expectedRange));
     expect(coverage['scripts'].length, 1);
     expect(
@@ -106,7 +106,7 @@
     };
     coverage = await isolate.invokeRpcNoUpgrade('getSourceReport', params);
     expect(coverage['type'], equals('SourceReport'));
-    expect(coverage['ranges'].length, 11);
+    expect(coverage['ranges'].length, 12);
     expect(allRangesCompiled(coverage), isTrue);
 
     // One function
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index 7a297eb..c92c5e5 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -109,6 +109,7 @@
 get_allocation_samples_test: Skip, Timeout
 get_isolate_after_language_error_test: CompileTimeError
 get_object_rpc_test: SkipByDesign
+get_source_report_const_coverage_test: SkipByDesign
 get_source_report_test: Skip, Timeout
 get_source_report_with_mixin_test: Skip, Timeout
 get_stack_limit_rpc_test: Skip, Timeout
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart
new file mode 100644
index 0000000..b0f078d
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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 "get_source_report_const_coverage_test.dart";
+
+void testFunction() {
+  const namedFoo = Foo.named3();
+  const namedFoo2 = Foo.named3();
+  const namedIdentical = identical(namedFoo, namedFoo2);
+  print("namedIdentical: $namedIdentical");
+}
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart
new file mode 100644
index 0000000..7c6af16
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, 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 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+import 'test_helper.dart';
+import 'service_test_common.dart';
+import 'dart:developer';
+
+import 'get_source_report_const_coverage_lib.dart' as lib;
+
+const String filename = "get_source_report_const_coverage_test";
+const Set<int> expectedLinesHit = {20, 22, 26};
+const Set<int> expectedLinesNotHit = {24};
+
+class Foo {
+  final int x;
+  // Expect this constructor to be coverage by coverage.
+  const Foo([int x]) : this.x = x ?? 42;
+  // Expect this constructor to be coverage by coverage too.
+  const Foo.named1([int x]) : this.x = x ?? 42;
+  // Expect this constructor to *NOT* be coverage by coverage.
+  const Foo.named2([int x]) : this.x = x ?? 42;
+  // Expect this constructor to be coverage by coverage too (from lib).
+  const Foo.named3([int x]) : this.x = x ?? 42;
+}
+
+void testFunction() {
+  const foo = Foo();
+  const foo2 = Foo();
+  const fooIdentical = identical(foo, foo2);
+  print(fooIdentical);
+
+  const namedFoo = Foo.named1();
+  const namedFoo2 = Foo.named1();
+  const namedIdentical = identical(namedFoo, namedFoo2);
+  print(fooIdentical);
+
+  debugger();
+
+  // That this is called after (or at all) is not relevent for the code
+  // coverage of constants.
+  lib.testFunction();
+
+  print("Done");
+}
+
+var tests = <IsolateTest>[
+  hasStoppedAtBreakpoint,
+  (Isolate isolate) async {
+    final stack = await isolate.getStack();
+
+    // Make sure we are in the right place.
+    expect(stack.type, equals('Stack'));
+    expect(stack['frames'].length, greaterThanOrEqualTo(1));
+    expect(stack['frames'][0].function.name, equals('testFunction'));
+
+    final List<Script> scripts = await isolate.getScripts();
+    Script foundScript;
+    for (Script script in scripts) {
+      if (script.uri.contains(filename)) {
+        foundScript = script;
+        break;
+      }
+    }
+
+    Set<int> hits;
+    {
+      // Get report for everything; then collect for this library.
+      final Map<String, Object> params = {
+        'reports': ['Coverage'],
+      };
+      final coverage =
+          await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+      hits = getHitsFor(coverage, filename);
+      await foundScript.load();
+      final Set<int> lines = {};
+      for (int hit in hits) {
+        // We expect every hit to be translatable to line
+        // (i.e. tokenToLine to return non-null).
+        lines.add(foundScript.tokenToLine(hit));
+      }
+      print("Token position hits: $hits --- line hits: $lines");
+      expect(lines.intersection(expectedLinesHit), equals(expectedLinesHit));
+      expect(lines.intersection(expectedLinesNotHit), isEmpty);
+    }
+    {
+      // Now get report for the this file only.
+      final Map<String, Object> params = {
+        'reports': ['Coverage'],
+        'scriptId': foundScript.id
+      };
+      final coverage =
+          await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+      final Set<int> localHits = getHitsFor(coverage, filename);
+      expect(localHits.length, equals(hits.length));
+      expect(hits.toList()..sort(), equals(localHits.toList()..sort()));
+      print(localHits);
+    }
+  },
+];
+
+Set<int> getHitsFor(Map coverage, String uriContains) {
+  final List scripts = coverage["scripts"];
+  final Set<int> scriptIdsWanted = {};
+  for (int i = 0; i < scripts.length; i++) {
+    final Map script = scripts[i];
+    final String scriptUri = script["uri"];
+    if (scriptUri.contains(uriContains)) {
+      scriptIdsWanted.add(i);
+    }
+  }
+  final List ranges = coverage["ranges"];
+  final Set<int> hits = {};
+  for (int i = 0; i < ranges.length; i++) {
+    final Map range = ranges[i];
+    if (scriptIdsWanted.contains(range["scriptIndex"])) {
+      if (range["coverage"] != null) {
+        for (int hit in range["coverage"]["hits"]) {
+          hits.add(hit);
+        }
+      }
+    }
+  }
+  return hits;
+}
+
+main(args) => runIsolateTests(args, tests, testeeConcurrent: testFunction);
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_test.dart b/runtime/observatory_2/tests/service_2/get_source_report_test.dart
index 97606de..78ea471 100644
--- a/runtime/observatory_2/tests/service_2/get_source_report_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_source_report_test.dart
@@ -91,7 +91,7 @@
     final numRanges = coverage['ranges'].length;
     expect(coverage['type'], equals('SourceReport'));
 
-    expect(numRanges, equals(10));
+    expect(numRanges, equals(11));
     expect(coverage['ranges'][0], equals(expectedRange));
     expect(coverage['scripts'].length, 1);
     expect(
@@ -106,7 +106,7 @@
     };
     coverage = await isolate.invokeRpcNoUpgrade('getSourceReport', params);
     expect(coverage['type'], equals('SourceReport'));
-    expect(coverage['ranges'].length, 11);
+    expect(coverage['ranges'].length, 12);
     expect(allRangesCompiled(coverage), isTrue);
 
     // One function
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index d8996be..e8d4784 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -109,6 +109,7 @@
 get_allocation_samples_test: Skip, Timeout
 get_isolate_after_language_error_test: CompileTimeError
 get_object_rpc_test: SkipByDesign
+get_source_report_const_coverage_test: SkipByDesign
 get_source_report_test: Skip, Timeout
 get_source_report_with_mixin_test: Skip, Timeout
 get_stack_limit_rpc_test: Skip, Timeout
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index e706302..6cc0183 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -2807,6 +2807,33 @@
   return H.DartString(reader_.BufferAt(ReaderOffset()), size, Heap::kOld);
 }
 
+ExternalTypedDataPtr KernelReaderHelper::GetConstantCoverageFor(
+    intptr_t index) {
+  AlternativeReadingScope alt(&reader_);
+  SetOffset(GetOffsetForSourceInfo(index));
+  SkipBytes(ReadUInt());                         // skip uri.
+  SkipBytes(ReadUInt());                         // skip source.
+  const intptr_t line_start_count = ReadUInt();  // read number of line start
+                                                 // entries.
+  for (intptr_t i = 0; i < line_start_count; ++i) {
+    ReadUInt();
+  }
+
+  SkipBytes(ReadUInt());  // skip import uri.
+
+  intptr_t start_offset = ReaderOffset();
+
+  // Read past "constant coverage constructors".
+  const intptr_t constant_coverage_constructors = ReadUInt();
+  for (intptr_t i = 0; i < constant_coverage_constructors; ++i) {
+    ReadUInt();
+  }
+
+  intptr_t end_offset = ReaderOffset();
+
+  return reader_.ExternalDataFromTo(start_offset, end_offset);
+}
+
 intptr_t ActiveClass::MemberTypeParameterCount(Zone* zone) {
   ASSERT(member != NULL);
   if (member->IsFactory()) {
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index d725cf7..28877d0 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -1244,6 +1244,7 @@
   const String& GetSourceFor(intptr_t index);
   TypedDataPtr GetLineStartsFor(intptr_t index);
   String& SourceTableImportUriFor(intptr_t index, uint32_t binaryVersion);
+  ExternalTypedDataPtr GetConstantCoverageFor(intptr_t index);
 
   Zone* zone_;
   TranslationHelper& translation_helper_;
@@ -1282,6 +1283,8 @@
   friend class ObfuscationProhibitionsMetadataHelper;
   friend class LoadingUnitsMetadataHelper;
   friend bool NeedsDynamicInvocationForwarder(const Function& function);
+  friend ArrayPtr CollectConstConstructorCoverageFrom(
+      const Script& interesting_script);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(KernelReaderHelper);
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index e4dae18..156a5c4 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -501,7 +501,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
-static constexpr dart::compiler::target::word Script_InstanceSize = 56;
+static constexpr dart::compiler::target::word Script_InstanceSize = 64;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 12;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -1022,7 +1022,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 104;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -1534,7 +1534,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
-static constexpr dart::compiler::target::word Script_InstanceSize = 56;
+static constexpr dart::compiler::target::word Script_InstanceSize = 64;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 12;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -2056,7 +2056,7 @@
 static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
 static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
 static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 104;
 static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
 static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
 static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
diff --git a/runtime/vm/kernel.cc b/runtime/vm/kernel.cc
index 7c53e72..07932e6 100644
--- a/runtime/vm/kernel.cc
+++ b/runtime/vm/kernel.cc
@@ -347,6 +347,36 @@
   script.set_debug_positions(array_object);
 }
 
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ArrayPtr CollectConstConstructorCoverageFrom(const Script& interesting_script) {
+  Thread* thread = Thread::Current();
+  Zone* zone = thread->zone();
+  interesting_script.LookupSourceAndLineStarts(zone);
+  TranslationHelper helper(thread);
+  helper.InitFromScript(interesting_script);
+
+  ExternalTypedData& data =
+      ExternalTypedData::Handle(zone, interesting_script.constant_coverage());
+
+  KernelReaderHelper kernel_reader(zone, &helper, interesting_script, data, 0);
+
+  // Read "constant coverage constructors".
+  const intptr_t constant_coverage_constructors = kernel_reader.ReadUInt();
+  const Array& constructors =
+      Array::Handle(Array::New(constant_coverage_constructors));
+  for (intptr_t i = 0; i < constant_coverage_constructors; ++i) {
+    NameIndex kernel_name = kernel_reader.ReadCanonicalNameReference();
+    Class& klass = Class::ZoneHandle(
+        zone,
+        helper.LookupClassByKernelClass(helper.EnclosingName(kernel_name)));
+    const Function& target = Function::ZoneHandle(
+        zone, helper.LookupConstructorByKernelConstructor(klass, kernel_name));
+    constructors.SetAt(i, target);
+  }
+  return constructors.raw();
+}
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
 ObjectPtr EvaluateStaticConstFieldInitializer(const Field& field) {
   ASSERT(field.is_static() && field.is_const());
 
diff --git a/runtime/vm/kernel.h b/runtime/vm/kernel.h
index 9b0d8de..4b55de9 100644
--- a/runtime/vm/kernel.h
+++ b/runtime/vm/kernel.h
@@ -195,6 +195,10 @@
 
 void CollectTokenPositionsFor(const Script& script);
 
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ArrayPtr CollectConstConstructorCoverageFrom(const Script& interesting_script);
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
 ObjectPtr EvaluateStaticConstFieldInitializer(const Field& field);
 ObjectPtr EvaluateMetadata(const Field& metadata_field,
                            bool is_annotations_offset);
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index e37f325..a8cafe4 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -2099,6 +2099,10 @@
   const String& uri_string = helper_.SourceTableUriFor(index);
   const String& import_uri_string =
       helper_.SourceTableImportUriFor(index, program_->binary_version());
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+  ExternalTypedData& constant_coverage =
+      ExternalTypedData::Handle(Z, helper_.GetConstantCoverageFor(index));
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
 
   String& sources = String::Handle(Z);
   TypedData& line_starts = TypedData::Handle(Z);
@@ -2144,6 +2148,9 @@
   script.set_kernel_script_index(index);
   script.set_kernel_program_info(kernel_program_info_);
   script.set_line_starts(line_starts);
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+  script.set_constant_coverage(constant_coverage);
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
   script.set_debug_positions(Array::null_array());
   return script.raw();
 }
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index a709577..bae9d93f 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -11162,6 +11162,16 @@
   StorePointer(&raw_ptr()->line_starts_, value.raw());
 }
 
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+void Script::set_constant_coverage(const ExternalTypedData& value) const {
+  StorePointer(&raw_ptr()->constant_coverage_, value.raw());
+}
+
+ExternalTypedDataPtr Script::constant_coverage() const {
+  return raw_ptr()->constant_coverage_;
+}
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
 void Script::set_debug_positions(const Array& value) const {
   StorePointer(&raw_ptr()->debug_positions_, value.raw());
 }
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 808d9e1..584e180 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -4501,6 +4501,12 @@
 
   TypedDataPtr line_starts() const;
 
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+  ExternalTypedDataPtr constant_coverage() const;
+
+  void set_constant_coverage(const ExternalTypedData& value) const;
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
   void set_line_starts(const TypedData& value) const;
 
   void set_debug_positions(const Array& value) const;
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 6ef0636..bbfc8f7 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -1253,6 +1253,9 @@
   StringPtr resolved_url_;
   ArrayPtr compile_time_constants_;
   TypedDataPtr line_starts_;
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+  ExternalTypedDataPtr constant_coverage_;
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
   ArrayPtr debug_positions_;
   KernelProgramInfoPtr kernel_program_info_;
   StringPtr source_;
@@ -1284,8 +1287,17 @@
                kLazyLookupSourceAndLineStartsSize>;
   uint8_t flags_;
 
-  intptr_t kernel_script_index_;
   int64_t load_timestamp_;
+  int32_t kernel_script_index_;
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) &&                 \
+    defined(ARCH_IS_32_BIT)
+  // MSVC aligns sizeof(ScriptLayout) by 8 on 32-bit because rgw structure
+  // contains int64_t member, while clang does not.
+  // This member is thus needed to ensure consistent struct size across all
+  // compilers.
+  int32_t padding_;
+#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) &&          \
+        // defined(ARCH_IS_32_BIT)
 };
 
 class LibraryLayout : public ObjectLayout {
diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc
index 84f5495..3f074ff 100644
--- a/runtime/vm/source_report.cc
+++ b/runtime/vm/source_report.cc
@@ -565,6 +565,19 @@
 
     // Visit all closures for this isolate.
     VisitClosures(&ranges);
+
+    // Output constant coverage if coverage is requested.
+    if (IsReportRequested(kCoverage)) {
+      // Find all scripts. We need to go though all scripts because a script
+      // (even one we don't want) can add coverage to another library (i.e.
+      // potentially one we want).
+      DirectChainedHashMap<ScriptTableTrait> local_script_table;
+      GrowableArray<ScriptTableEntry*> local_script_table_entries;
+      CollectAllScripts(&local_script_table, &local_script_table_entries);
+      CollectConstConstructorCoverageFromScripts(&local_script_table_entries,
+                                                 &ranges);
+      CleanupCollectedScripts(&local_script_table, &local_script_table_entries);
+    }
   }
 
   // Print the script table.
@@ -572,5 +585,102 @@
   PrintScriptTable(&scripts);
 }
 
+void SourceReport::CollectAllScripts(
+    DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+    GrowableArray<ScriptTableEntry*>* local_script_table_entries) {
+  ScriptTableEntry wrapper;
+  const GrowableObjectArray& libs = GrowableObjectArray::Handle(
+      zone(), thread()->isolate()->object_store()->libraries());
+  Library& lib = Library::Handle(zone());
+  Script& scriptRef = Script::Handle(zone());
+  for (int i = 0; i < libs.Length(); i++) {
+    lib ^= libs.At(i);
+    const Array& scripts = Array::Handle(zone(), lib.LoadedScripts());
+    for (intptr_t j = 0; j < scripts.Length(); j++) {
+      scriptRef ^= scripts.At(j);
+      const String& url = String::Handle(zone(), scriptRef.url());
+      wrapper.key = &url;
+      wrapper.script = &Script::Handle(zone(), scriptRef.raw());
+      ScriptTableEntry* pair = local_script_table->LookupValue(&wrapper);
+      if (pair != NULL) {
+        // Existing one.
+        continue;
+      }
+      // New one. Insert.
+      ScriptTableEntry* tmp = new ScriptTableEntry();
+      tmp->key = &url;
+      tmp->index = next_script_index_++;
+      tmp->script = wrapper.script;
+      local_script_table_entries->Add(tmp);
+      local_script_table->Insert(tmp);
+    }
+  }
+}
+
+void SourceReport::CleanupCollectedScripts(
+    DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+    GrowableArray<ScriptTableEntry*>* local_script_table_entries) {
+  for (intptr_t i = 0; i < local_script_table_entries->length(); i++) {
+    delete local_script_table_entries->operator[](i);
+    local_script_table_entries->operator[](i) = NULL;
+  }
+  local_script_table_entries->Clear();
+  local_script_table->Clear();
+}
+
+void SourceReport::CollectConstConstructorCoverageFromScripts(
+    GrowableArray<ScriptTableEntry*>* local_script_table_entries,
+    JSONArray* ranges) {
+  // Now output the wanted constant coverage.
+  for (intptr_t i = 0; i < local_script_table_entries->length(); i++) {
+    const Script* script = local_script_table_entries->At(i)->script;
+    bool script_ok = true;
+    if (script_ != NULL && !script_->IsNull()) {
+      if (script->raw() != script_->raw()) {
+        // This is the wrong script.
+        script_ok = false;
+      }
+    }
+
+    // Whether we want *this* script or not we need to look at the constant
+    // constructor coverage. Any of those could be in a script we *do* want.
+    {
+      Script& scriptRef = Script::Handle(zone());
+      const Array& constructors =
+          Array::Handle(kernel::CollectConstConstructorCoverageFrom(*script));
+      intptr_t constructors_count = constructors.Length();
+      Function& constructor = Function::Handle(zone());
+      Code& code = Code::Handle(zone());
+      for (intptr_t i = 0; i < constructors_count; i++) {
+        constructor ^= constructors.At(i);
+        // Check if we want coverage for this constructor.
+        if (ShouldSkipFunction(constructor)) {
+          continue;
+        }
+        scriptRef ^= constructor.script();
+        code ^= constructor.unoptimized_code();
+        const TokenPosition begin_pos = constructor.token_pos();
+        const TokenPosition end_pos = constructor.end_token_pos();
+        JSONObject range(ranges);
+        range.AddProperty("scriptIndex", GetScriptIndex(scriptRef));
+        range.AddProperty("compiled",
+                          !code.IsNull());  // Does this make a difference?
+        range.AddProperty("startPos", begin_pos);
+        range.AddProperty("endPos", end_pos);
+
+        JSONObject cov(&range, "coverage");
+        {
+          JSONArray hits(&cov, "hits");
+          hits.AddValue(begin_pos);
+        }
+        {
+          JSONArray misses(&cov, "misses");
+          // No misses
+        }
+      }
+    }
+  }
+}
+
 }  // namespace dart
 #endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/source_report.h b/runtime/vm/source_report.h
index 0a3ddb1..4ec20c9 100644
--- a/runtime/vm/source_report.h
+++ b/runtime/vm/source_report.h
@@ -112,6 +112,18 @@
     }
   };
 
+  void CollectAllScripts(
+      DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+      GrowableArray<ScriptTableEntry*>* local_script_table_entries);
+
+  void CleanupCollectedScripts(
+      DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+      GrowableArray<ScriptTableEntry*>* local_script_table_entries);
+
+  void CollectConstConstructorCoverageFromScripts(
+      GrowableArray<ScriptTableEntry*>* local_script_table_entries,
+      JSONArray* ranges);
+
   intptr_t report_set_;
   CompileMode compile_mode_;
   Thread* thread_;