[kernel] Add 'dill_forensic' tool

Tool will try to extract useful information from invalid (e.g. partial
or wrong version) dill.
This is a first stab and could possibly be extended and improved in the
future.

Change-Id: Ib381794a3fe80036bb845800488fa0e1a7f04f83
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/211241
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/kernel/bin/dill_forensic.dart b/pkg/kernel/bin/dill_forensic.dart
new file mode 100755
index 0000000..54c1bab
--- /dev/null
+++ b/pkg/kernel/bin/dill_forensic.dart
@@ -0,0 +1,106 @@
+#!/usr/bin/env dart
+// Copyright (c) 2021, 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 'dart:io';
+import 'dart:typed_data';
+
+import 'package:kernel/kernel.dart'
+    show Component, Source, loadComponentFromBytes;
+import 'package:kernel/binary/tag.dart' show Tag;
+
+main(List<String> args) {
+  if (args.length != 1) {
+    throw "Usage: dart <script> <dillfile>";
+  }
+  File file = new File(args.single);
+  if (!file.existsSync()) {
+    throw "Given file doesn't exist.\n"
+        "Usage: dart <script> <dillfile>";
+  }
+  Uint8List bytes = file.readAsBytesSync();
+  List<Component> components = splitAndRead(bytes);
+  print("Sucessfully read ${components.length} sub-components.");
+
+  for (int i = 0; i < components.length; i++) {
+    Component component = components[i];
+    print("Component #${i + 1}:");
+    for (MapEntry<Uri, Source> entry in component.uriToSource.entries) {
+      String importUri = entry.value.importUri.toString();
+      String fileUri = entry.key.toString();
+      if (importUri != fileUri) {
+        print(" - $importUri ($fileUri)");
+      } else {
+        print(" - $fileUri");
+      }
+    }
+  }
+
+  // TODO(jensj): Could we read _some_ (useful) data from a partial component
+  // (e.g. one that stops after the first few libraries)?
+}
+
+List<Component> splitAndRead(Uint8List bytes) {
+  List<Component> components = [];
+
+  // Search for magic component file tag.
+  List<int> magicTagBytes = [
+    (Tag.ComponentFile >> 24) & 0XFF,
+    (Tag.ComponentFile >> 16) & 0XFF,
+    (Tag.ComponentFile >> 8) & 0XFF,
+    (Tag.ComponentFile >> 0) & 0XFF,
+  ];
+  List<int> tagOffsets = [];
+  for (int index = 0; index < bytes.length - 7; index++) {
+    if (bytes[index] == magicTagBytes[0] &&
+        bytes[index + 1] == magicTagBytes[1] &&
+        bytes[index + 2] == magicTagBytes[2] &&
+        bytes[index + 3] == magicTagBytes[3]) {
+      // Try to read binary version too and see if it matches.
+      int version = (bytes[index + 4] << 24) |
+          (bytes[index + 5] << 16) |
+          (bytes[index + 6] << 8) |
+          bytes[index + 7];
+      if (version != Tag.BinaryFormatVersion) {
+        print("Found tag, but version mismatches "
+            "('$version' vs readable version '${Tag.BinaryFormatVersion}'). "
+            "Try again in a different checkout.");
+      } else {
+        tagOffsets.add(index);
+      }
+    }
+  }
+  print("Found ${tagOffsets.length} possible tag offsets.");
+
+  // Add fake file tag after end of bytes to also attempt "last component".
+  tagOffsets.add(bytes.length);
+
+  // Warning: O(n²) algorithm (though, as the tag is assumed to be rather unique
+  // in normal cases it will probably much better in practise
+  // (and n will be low)).
+  int fromIndex = 0;
+  while (fromIndex < tagOffsets.length - 1) {
+    int toIndex = fromIndex + 1;
+    while (toIndex < tagOffsets.length) {
+      // Cut bytes and try to load.
+      int fromOffset = tagOffsets[fromIndex];
+      int toOffset = tagOffsets[toIndex];
+      Uint8List bytesView =
+          new Uint8List.sublistView(bytes, fromOffset, toOffset);
+      try {
+        Component loaded = loadComponentFromBytes(bytesView);
+        components.add(loaded);
+        print("Loaded from tag ${fromIndex} to ${toIndex}.");
+        break;
+      } catch (e) {
+        print("Failed loading from tag ${fromIndex} to ${toIndex}"
+            " (${toOffset - fromOffset} bytes).");
+        toIndex++;
+      }
+    }
+    fromIndex++;
+  }
+
+  return components;
+}