blob: 3fcc12d43e1125438130b3b03f5c993d372c574c [file] [log] [blame]
// Copyright (c) 2019, 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:math" as math;
class BinaryMdDillReader {
final String _binaryMdContent;
/// The actual binary content.
final List<int> _dillContent;
String _currentlyUnparsed = "";
late Map<String, List<String>> readingInstructions;
late Map<String, List<String>> _generics;
late Set<String> _abstractTypes;
late Map<int, String> tagToName;
late Map<int, String> constantTagToName;
int? version;
late Map<String, String> _extends;
late int _binaryMdNestingDepth;
late String _binaryMdCurrentClass;
/// The offset in the binary where we're supposed to read next.
late int _binaryOffset;
late int _depth;
late Map _dillStringsPointer;
int verboseLevel = 0;
bool _ranSetup = false;
List<String> readingStack = [];
BinaryMdDillReader(this._binaryMdContent, this._dillContent);
void setup() {
if (!_ranSetup) {
_setupFields();
_readBinaryMd();
_ranSetup = true;
}
}
Map attemptRead() {
setup();
return _readDill();
}
/// Initialize the bare essentials, e.g. that a double is 8 bytes.
void _setupFields() {
readingInstructions = {
"Byte": ["byte"],
"UInt32": ["byte", "byte", "byte", "byte"],
"Double": ["byte", "byte", "byte", "byte", "byte", "byte", "byte", "byte"]
};
_generics = {};
_abstractTypes = {};
tagToName = {};
constantTagToName = {};
_extends = {};
_binaryMdNestingDepth = 0;
_binaryMdCurrentClass = "";
}
/// Read the binary.md text and put the data into the various tables.
void _readBinaryMd() {
List<String> lines = _binaryMdContent.split("\n");
bool inComment = false;
for (String s in lines) {
if (s.trim().startsWith("//") || s.trim() == "") {
continue;
} else if (s.trim().startsWith("/*")) {
inComment = true;
continue;
} else if (s.trim().startsWith("*/")) {
inComment = false;
continue;
} else if (inComment) {
continue;
} else if (s.trim().startsWith("type ") ||
s.trim().startsWith("abstract type ") ||
s.trim().startsWith("enum ")) {
_binaryMdHandlePossibleClassStart(s);
} else if (s.trim() == "if name begins with '_' {" &&
_binaryMdCurrentClass == "Name") {
// Special-case if sentence in Name.
_binaryMdNestingDepth++;
} else if (s.trim().endsWith("{")) {
throw "Unhandled case: $s";
} else if (s.trim() == "}") {
_binaryMdNestingDepth--;
_binaryMdCurrentClass = "";
} else if (_binaryMdNestingDepth > 0 && _binaryMdCurrentClass != "") {
_binaryMdHandleContent(s);
}
}
_binaryMdCheckHasAllTypes();
if (verboseLevel > 0) {
print("Seems to find all types.");
}
}
late int numLibs;
late int binaryOffsetForSourceTable;
late int binaryOffsetForConstantTable;
late int binaryOffsetForConstantTableIndex;
late int binaryOffsetForCanonicalNames;
late int binaryOffsetForMetadataPayloads;
late int binaryOffsetForMetadataMappings;
late int binaryOffsetForStringTable;
late int binaryOffsetForStartOfComponentIndex;
late int mainMethodReference;
/// Read the dill file data, parsing it into a Map.
Map _readDill() {
_binaryOffset = 0;
_depth = 0;
// Hack start: Read ComponentIndex first.
_binaryOffset = _dillContent.length - (4 * 2);
numLibs = _peekUint32();
// Skip to the start of the index.
_binaryOffset = _dillContent.length -
((numLibs + 1) + 12 /* number of fixed fields */) * 4;
// Read index.
binaryOffsetForSourceTable = _peekUint32();
_binaryOffset += 4;
binaryOffsetForConstantTable = _peekUint32();
_binaryOffset += 4;
binaryOffsetForConstantTableIndex = _peekUint32();
_binaryOffset += 4;
binaryOffsetForCanonicalNames = _peekUint32();
_binaryOffset += 4;
binaryOffsetForMetadataPayloads = _peekUint32();
_binaryOffset += 4;
binaryOffsetForMetadataMappings = _peekUint32();
_binaryOffset += 4;
binaryOffsetForStringTable = _peekUint32();
_binaryOffset += 4;
binaryOffsetForStartOfComponentIndex = _peekUint32();
_binaryOffset += 4;
mainMethodReference = _peekUint32();
_binaryOffset += 4;
/*int compilationMode = */ _peekUint32();
_binaryOffset = binaryOffsetForStringTable;
var saved = readingInstructions["ComponentFile"]!;
readingInstructions["ComponentFile"] = ["StringTable strings;"];
_readBinary("ComponentFile", "");
readingInstructions["ComponentFile"] = saved;
_binaryOffset = 0;
_depth = 0;
// Hack end.
Map componentFile = _readBinary("ComponentFile", "");
if (_binaryOffset != _dillContent.length) {
throw "Didn't read the entire binary: "
"Only read $_binaryOffset of ${_dillContent.length} bytes. "
"($componentFile)";
}
if (verboseLevel > 0) {
print("Successfully read the dill file.");
}
return componentFile;
}
/// Initial setup of a "class definition" in the binary.md file.
/// This includes parsing the name, setting up any "extends"-relationship,
/// generics etc.
void _binaryMdHandlePossibleClassStart(String s) {
if (s.startsWith("type Byte =")) return;
if (s.startsWith("type UInt32 =")) return;
if (_binaryMdNestingDepth != 0 || _binaryMdCurrentClass != "") {
throw "Cannot handle nesting: "
"'$s', $_binaryMdNestingDepth, $_binaryMdCurrentClass";
}
if (s.contains("{")) _binaryMdNestingDepth++;
if (s.contains("}")) _binaryMdNestingDepth--;
String name = s.trim();
bool isAbstract = name.startsWith("abstract ");
if (isAbstract) {
name = name.substring("abstract ".length);
}
if (name.startsWith("type ")) name = name.substring("type ".length);
bool isEnum = false;
if (name.startsWith("enum ")) {
name = name.substring("enum ".length);
isEnum = true;
}
String? nameExtends = null;
Match? extendsMatch = (new RegExp("extends (.+)[ \{]")).firstMatch(name);
if (extendsMatch != null) {
nameExtends = extendsMatch.group(1);
}
name = _getType(name);
if (name.contains("<")) {
List<String> types = _getGenerics(name);
name = name.substring(0, name.indexOf("<")) + "<${types.length}>";
_generics[name] ??= types;
}
if (_binaryMdNestingDepth != 0) _binaryMdCurrentClass = name;
if (nameExtends != null) {
_extends[name] = nameExtends.trim();
}
if (isEnum) {
readingInstructions[name] ??= ["byte"];
} else {
readingInstructions[name] ??= [];
}
if (isAbstract) {
_abstractTypes.add(name);
}
}
Map<String, String> _typeCache = {};
/// Extract the type/name of an input string, e.g. turns
///
/// * "ClassLevel { Type = 0, [...], }" into "ClassLevel"
/// * "Class extends Node {" into "Class"
/// * "Byte tag = 97;" into "Byte"
/// * "List<T> {" into "List<T>"
String _getType(final String inputString) {
String? cached = _typeCache[inputString];
if (cached != null) return cached;
int end = math.max(
math.max(inputString.indexOf(" "), inputString.lastIndexOf(">") + 1),
inputString.lastIndexOf("]") + 1);
if (end <= 0) end = inputString.length;
String result = inputString.substring(0, end);
if (result.contains(" extends")) {
result = result.substring(0, result.indexOf(" extends "));
}
_typeCache[inputString] = result;
return result;
}
static const int $COMMA = 44;
static const int $LT = 60;
static const int $GT = 62;
/// Extract the generics used in an input type, e.g. turns
///
/// * "Pair<A, B>" into ["A", "B"].
/// * "List<Expression>" into ["Expression"].
/// * "Foo<Bar<Baz>>" into ["Bar<Baz>"].
/// * "Foo<A, B<C, D>, E>" into ["A", "B<C, D>", "E"].
///
/// Note that the input string *has* to use generics, i.e. have '<' and '>'
/// in it.
///
/// DartDocTest(
/// BinaryMdDillReader._getGenerics("Pair<A, B>"), ["A", "B"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics("List<Expression>"), ["Expression"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics("List<Pair<FileOffset, Expression>>"),
/// ["Pair<FileOffset, Expression>"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics("RList<Pair<UInt32, UInt32>>"),
/// ["Pair<UInt32, UInt32>"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics(
/// "List<Pair<FieldReference, Expression>>"),
/// ["Pair<FieldReference, Expression>"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics(
/// "List<Pair<ConstantReference, ConstantReference>>"),
/// ["Pair<ConstantReference, ConstantReference>"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics(
/// "List<Pair<FieldReference, ConstantReference>>"),
/// ["Pair<FieldReference, ConstantReference>"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics("Option<List<DartType>>"),
/// ["List<DartType>"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics("Foo<Bar<Baz>>"), ["Bar<Baz>"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics("Foo<A, B<C, D>, E>"),
/// ["A", "B<C, D>", "E"]
/// )
/// DartDocTest(
/// BinaryMdDillReader._getGenerics("Foo<A, B<C, D<E<F<G>>>>, H>"),
/// ["A", "B<C, D<E<F<G>>>>", "H"]
/// )
///
/// Expected failing run (expects to fail with unbalanced < >).
/// TODO: Support this more elegantly.
/// DartDocTest(() {
/// try {
/// BinaryMdDillReader._getGenerics("Foo<A, B<C, D, E>");
/// return false;
/// } catch(e) {
/// return true;
/// }
/// }(),
/// true
/// )
///
static List<String> _getGenerics(String s) {
s = s.substring(s.indexOf("<") + 1, s.lastIndexOf(">"));
// Check that any '<' and '>' are balanced and split entries on comma for
// the outermost parameters.
int ltCount = 0;
int gtCount = 0;
int depth = 0;
int lastPos = 0;
List<int> codeUnits = s.codeUnits;
List<String> result = [];
for (int i = 0; i < codeUnits.length; i++) {
int codeUnit = codeUnits[i];
if (codeUnit == $LT) {
ltCount++;
depth++;
} else if (codeUnit == $GT) {
gtCount++;
depth--;
} else if (codeUnit == $COMMA && depth == 0) {
result.add(s.substring(lastPos, i).trim());
lastPos = i + 1;
}
}
if (ltCount != gtCount) {
throw "Unbalanced '<' and '>': $s";
}
assert(depth == 0);
result.add(s.substring(lastPos, codeUnits.length).trim());
return result;
}
/// Parses a line of binary.md content for a "current class" into the
/// reading-instructions for that class.
/// There is special handling around tags, and around lines that are split,
/// i.e. not yet finished (not ending in a semi-colon).
void _binaryMdHandleContent(String s) {
if (s.trim().startsWith("UInt32 formatVersion = ")) {
String versionString =
s.trim().substring("UInt32 formatVersion = ".length);
if (versionString.endsWith(";")) {
versionString = versionString.substring(0, versionString.length - 1);
}
if (version != null) {
throw "Already have a version set ($version), "
"now trying to set $versionString";
}
version = int.parse(versionString);
}
if (s.trim().startsWith("Byte tag = ")) {
String tag = s.trim().substring("Byte tag = ".length);
if (tag.endsWith(";")) tag = tag.substring(0, tag.length - 1);
if (tag == "224 + N; // Where 0 <= N < 8.") {
for (int n = 0; n < 8; ++n) {
tagToName[224 + n] = _binaryMdCurrentClass;
}
} else if (tag == "232 + N; // Where 0 <= N < 8.") {
for (int n = 0; n < 8; ++n) {
tagToName[232 + n] = _binaryMdCurrentClass;
}
} else if (tag == "240 + N; // Where 0 <= N < 8.") {
for (int n = 0; n < 8; ++n) {
tagToName[240 + n] = _binaryMdCurrentClass;
}
} else {
if (tag.contains("; // Note: tag is out of order")) {
tag = tag.substring(0, tag.indexOf("; // Note: tag is out of order"));
}
Map<int, String> tagMap;
if (isA(_binaryMdCurrentClass, "Constant")) {
tagMap = constantTagToName;
} else {
tagMap = tagToName;
}
if (tagMap[int.parse(tag)] != null) {
throw "Two tags with same name!: "
"$tag (${tagMap[int.parse(tag)]} and ${_binaryMdCurrentClass})";
}
tagMap[int.parse(tag)] = _binaryMdCurrentClass;
}
}
{
var line = _currentlyUnparsed + s.trim();
if (line.contains("//")) line = line.substring(0, line.indexOf("//"));
if (!line.trim().endsWith(";")) {
_currentlyUnparsed = line;
return;
}
s = line;
_currentlyUnparsed = "";
}
readingInstructions[_binaryMdCurrentClass]!.add(s.trim());
}
/// Check the all types referenced by reading instructions are types we know
/// about.
void _binaryMdCheckHasAllTypes() {
for (String key in readingInstructions.keys) {
for (String s in readingInstructions[key]!) {
String type = _getType(s);
if (!_isKnownType(type, key)) {
throw "Unknown type: $type (used in $key)";
}
}
}
}
/// Check that we know about the specific type, i.e. know something about how
/// to read it.
bool _isKnownType(String type, String parent) {
if (type == "byte") return true;
if (readingInstructions[type] != null) return true;
if (type.contains("[") &&
readingInstructions[type.substring(0, type.indexOf("["))] != null) {
return true;
}
if (parent.contains("<")) {
Set<String> types = _generics[parent]!.toSet();
if (types.contains(type)) return true;
if (type.contains("[") &&
types.contains(type.substring(0, type.indexOf("[")))) {
return true;
}
}
if (type.contains("<")) {
List<String> types = _getGenerics(type);
String renamedType =
type.substring(0, type.indexOf("<")) + "<${types.length}>";
if (readingInstructions[renamedType] != null) {
bool ok = true;
for (String type in types) {
if (!_isKnownType(type, renamedType)) {
ok = false;
break;
}
}
if (ok) return true;
}
}
return false;
}
/// Get a string from the string table after the string table has been read
/// from the dill file.
String getDillString(int num) {
List<int> endOffsets =
(_dillStringsPointer["endOffsets"]["items"] as List<dynamic>).cast();
List<int> utf8 = (_dillStringsPointer["utf8Bytes"] as List<dynamic>).cast();
return new String.fromCharCodes(
utf8.sublist(num == 0 ? 0 : endOffsets[num - 1], endOffsets[num]));
}
RegExp regExpSplit = new RegExp(r"[\. ]");
/// Actually read the binary dill file. Read type [what] at the current
/// binary position as specified by field [_binaryOffset].
dynamic _readBinary(String what, String callerInstruction) {
++_depth;
what = _remapWhat(what);
// Read any 'base types'.
if (what == "UInt") {
return _readUint();
}
if (what == "UInt32") {
return _readUint32();
}
if (what == "byte" || what == "Byte") {
int value = _dillContent[_binaryOffset];
++_binaryOffset;
--_depth;
return value;
}
// Not a 'base type'. Read according to [readingInstructions] field.
List<String> types = [];
List<String> typeNames = [];
String orgWhat = what;
int orgPosition = _binaryOffset;
if (what.contains("<")) {
types = _getGenerics(what);
what = what.substring(0, what.indexOf("<")) + "<${types.length}>";
typeNames = _generics[what]!;
}
if (readingInstructions[what] == null) {
throw "Didn't find instructions for '$what'";
}
readingStack.add(orgWhat);
readingStack.add(callerInstruction);
Map<String, dynamic> vars = {};
if (verboseLevel > 1) {
print("".padLeft(_depth * 2) + " -> $what ($orgWhat @ $orgPosition)");
}
for (String instruction in readingInstructions[what]!) {
// Special-case a few things that aren't (easily) described in the
// binary.md file.
if (what == "Name" && instruction == "LibraryReference library;") {
// Special-case if sentence in Name.
String name = getDillString(vars["name"]["index"]);
if (!name.startsWith("_")) continue;
} else if (what == "ComponentFile" &&
instruction == "MetadataPayload[] metadataPayloads;") {
// Special-case skipping metadata payloads.
_binaryOffset = binaryOffsetForMetadataMappings;
continue;
} else if (what == "ComponentFile" &&
instruction == "RList<MetadataMapping> metadataMappings;") {
// Special-case skipping metadata mappings.
_binaryOffset = binaryOffsetForStringTable;
continue;
} else if (what == "ComponentIndex" &&
instruction == "Byte[] 8bitAlignment;") {
// Special-case 8-byte alignment.
int sizeWithoutPadding = _binaryOffset +
((numLibs + 1) + 10 /* number of fixed fields */) * 4;
int padding = 8 - sizeWithoutPadding % 8;
if (padding == 8) padding = 0;
_binaryOffset += padding;
continue;
}
String type = _getType(instruction);
String name = instruction.substring(type.length).trim();
if (name.contains("//")) {
name = name.substring(0, name.indexOf("//")).trim();
}
if (name.contains("=")) {
name = name.substring(0, name.indexOf("=")).trim();
}
if (name.endsWith(";")) name = name.substring(0, name.length - 1);
int oldOffset = _binaryOffset;
if (verboseLevel > 1) {
print("".padLeft(_depth * 2 + 1) +
" -> $instruction ($type) (@ $_binaryOffset) "
"($orgWhat @ $orgPosition)");
}
bool readNothingIsOk = false;
if (type.contains("[")) {
// The type is an array. Read into a List.
// Note that we need to know the length of that list.
String count = type.substring(type.indexOf("[") + 1, type.indexOf("]"));
type = type.substring(0, type.indexOf("["));
type = _lookupGenericType(typeNames, type, types);
int intCount = int.tryParse(count) ?? -1;
if (intCount == -1) {
if (vars[count] != null && vars[count] is int) {
intCount = vars[count];
} else if (count.contains(".")) {
List<String> countData =
count.split(regExpSplit).map((s) => s.trim()).toList();
if (vars[countData[0]] != null) {
dynamic v = vars[countData[0]];
if (v is Map &&
countData[1] == "last" &&
v["items"] is List &&
v["items"].last is int) {
intCount = v["items"].last;
} else if (v is Map && v[countData[1]] != null) {
v = v[countData[1]];
if (v is Map && v[countData[2]] != null) {
v = v[countData[2]];
if (v is int) intCount = v;
} else if (v is int &&
countData.length == 4 &&
countData[2] == "+") {
intCount = v + int.parse(countData[3]);
}
} else {
throw "Unknown dot to int ($count)";
}
}
}
}
// Special-case that we know how many libraries we have.
if (intCount < 0 && type == "Library" && _depth == 1) {
intCount = numLibs;
}
if (intCount < 0 &&
type == "UInt32" &&
_depth == 2 &&
count == "libraryCount + 1") {
intCount = numLibs + 1;
}
if (intCount < 0 &&
type == "UInt32" &&
_depth == 2 &&
count == "length" &&
readingStack.last == "RList<UInt32> constantsMapping;") {
int prevBinaryOffset = _binaryOffset;
// Hack: Use the ComponentIndex to go to the end and read the length.
_binaryOffset = binaryOffsetForCanonicalNames - 4;
intCount = _peekUint32();
_binaryOffset = prevBinaryOffset;
}
if (intCount >= 0) {
readNothingIsOk = intCount == 0;
List<dynamic> value = new List.filled(intCount, null);
for (int i = 0; i < intCount; ++i) {
int oldOffset2 = _binaryOffset;
value[i] = _readBinary(type, instruction);
if (_binaryOffset <= oldOffset2) {
throw "Didn't read anything for $type @ $_binaryOffset";
}
}
vars[name] = value;
} else {
throw "Array of unknown size ($instruction, $type, $count)";
}
} else {
// Not an array, read the single field recursively.
type = _lookupGenericType(typeNames, type, types);
dynamic value = _readBinary(type, instruction);
vars[name] = value;
_checkTag(instruction, value);
}
if (_binaryOffset <= oldOffset && !readNothingIsOk) {
throw "Didn't read anything for $type @ $_binaryOffset";
}
// Special case that when we read the string table we need to remember it
// to be able to lookup strings to read names properly later
// (private names has a library, public names does not).
if (what == "ComponentFile") {
if (name == "strings") _dillStringsPointer = vars[name];
}
}
--_depth;
readingStack.removeLast();
readingStack.removeLast();
return vars;
}
/// Verify, that if the instruction was a tag with a value
/// (e.g. "Byte tag = 5;"), then the value read was indeed the expected value
/// (5 in this example).
void _checkTag(String instruction, dynamic value) {
if (instruction.trim().startsWith("Byte tag = ")) {
String tag = instruction.trim().substring("Byte tag = ".length);
if (tag.contains("//")) {
tag = tag.substring(0, tag.indexOf("//")).trim();
}
if (tag.endsWith(";")) tag = tag.substring(0, tag.length - 1).trim();
int tagAsInt = int.tryParse(tag) ?? -1;
if (tagAsInt >= 0) {
if (tagAsInt != value) {
throw "Unexpected tag. "
"Expected $tagAsInt but got $value (around $_binaryOffset).";
}
}
}
}
/// Looks up any generics used, replacing the generic-name (if any) with the
/// actual type, e.g.
/// * ([], "UInt", []) into "UInt"
/// * (["T"], "T", ["Expression"]) into "Expression"
/// * (["T0", "T1"], "T0", ["FileOffset", "Expression"]) into "FileOffset"
String _lookupGenericType(
List<String> typeNames, String type, List<String> types) {
for (int i = 0; i < typeNames.length; ++i) {
if (typeNames[i] == type) {
type = types[i];
break;
}
}
return type;
}
bool isAbstract(String clazz) => _abstractTypes.contains(clazz);
/// Check if [what] is an [a], i.e. if [what] extends [a].
/// This method uses the [_extends] map and it is thus risky to use it before
/// the binary.md file has been read in entirety (because the field isn't
/// completely filled out yet).
bool isA(String what, String a) {
String? parent = what;
while (parent != null) {
if (parent == a) return true;
parent = _extends[parent];
}
return false;
}
/// Remaps the type by looking at tags, e.g. if asked to read an "Expression"
/// and the tag actually says "Block", return "Block" after checking that a
/// "Block" is actually an "Expression".
String _remapWhat(String what) {
Map<int, String> tagMap;
if (isA(what, "Constant")) {
tagMap = constantTagToName;
} else {
tagMap = tagToName;
}
if (what == "Expression") {
if (tagMap[_dillContent[_binaryOffset]] != null) {
what = tagMap[_dillContent[_binaryOffset]]!;
if (!isA(what, "Expression")) {
throw "Expected Expression but found $what";
}
} else {
throw "Unknown expression";
}
}
if (what == "IntegerLiteral") {
if (tagMap[_dillContent[_binaryOffset]] != null) {
what = tagMap[_dillContent[_binaryOffset]]!;
if (!isA(what, "IntegerLiteral")) {
throw "Expected IntegerLiteral but found $what";
}
} else {
throw "Unknown IntegerLiteral";
}
}
if (what == "Statement") {
if (tagMap[_dillContent[_binaryOffset]] != null) {
what = tagMap[_dillContent[_binaryOffset]]!;
if (!isA(what, "Statement")) {
throw "Expected Statement but found $what";
}
} else {
throw "Unknown Statement";
}
}
if (what == "Initializer") {
if (tagMap[_dillContent[_binaryOffset]] != null) {
what = tagMap[_dillContent[_binaryOffset]]!;
if (!isA(what, "Initializer")) {
throw "Expected Initializer but found $what";
}
} else {
throw "Unknown Initializer";
}
}
if (what == "DartType") {
if (tagMap[_dillContent[_binaryOffset]] != null) {
what = tagMap[_dillContent[_binaryOffset]]!;
if (!isA(what, "DartType")) {
throw "Expected DartType but found $what";
}
} else {
throw "Unknown DartType at $_binaryOffset "
"(${_dillContent[_binaryOffset]})";
}
}
if (what.startsWith("Option<")) {
if (tagMap[_dillContent[_binaryOffset]] != null &&
tagMap[_dillContent[_binaryOffset]]!.startsWith("Something<")) {
what = what.replaceFirst("Option<", "Something<");
}
}
if (what == "Constant") {
if (tagMap[_dillContent[_binaryOffset]] != null) {
what = tagMap[_dillContent[_binaryOffset]]!;
if (!isA(what, "Constant")) {
throw "Expected Constant but found $what";
}
} else {
throw "Unknown Constant";
}
}
return what;
}
/// Read the "UInt" type as used in kernel. This is hard-coded.
/// Note that this decrements the [_depth] and increments the
/// [_binaryOffset] correctly.
int _readUint() {
int b = _dillContent[_binaryOffset];
if (b & 128 == 0) {
++_binaryOffset;
--_depth;
return b;
}
if (b & 192 == 128) {
int value = (_dillContent[_binaryOffset] & 63) << 8 |
_dillContent[_binaryOffset + 1];
_binaryOffset += 2;
--_depth;
return value;
}
if (b & 192 == 192) {
int value = (_dillContent[_binaryOffset] & 63) << 24 |
_dillContent[_binaryOffset + 1] << 16 |
_dillContent[_binaryOffset + 2] << 8 |
_dillContent[_binaryOffset + 3];
_binaryOffset += 4;
--_depth;
return value;
}
throw "Unexpected UInt";
}
/// Read the "UInt43" type as used in kernel. This is hard-coded.
/// Note that this decrements the [_depth] and increments the
/// [_binaryOffset] correctly.
int _readUint32() {
int value = (_dillContent[_binaryOffset] & 63) << 24 |
_dillContent[_binaryOffset + 1] << 16 |
_dillContent[_binaryOffset + 2] << 8 |
_dillContent[_binaryOffset + 3];
_binaryOffset += 4;
--_depth;
return value;
}
/// Read the "UInt32" type as used in kernel. This is hard-coded.
/// This does not change any state.
int _peekUint32() {
return (_dillContent[_binaryOffset] & 63) << 24 |
_dillContent[_binaryOffset + 1] << 16 |
_dillContent[_binaryOffset + 2] << 8 |
_dillContent[_binaryOffset + 3];
}
}
class DillComparer {
Map<int, String>? tagToName;
StringBuffer? outputTo;
bool compare(List<int> a, List<int> b, String binaryMd,
[StringBuffer? outputTo]) {
this.outputTo = outputTo;
bool printOnExit = false;
if (this.outputTo == null) {
this.outputTo = new StringBuffer();
printOnExit = true;
}
BinaryMdDillReader readerA = new BinaryMdDillReader(binaryMd, a);
dynamic aResult = readerA.attemptRead();
tagToName = readerA.tagToName;
BinaryMdDillReader readerB = new BinaryMdDillReader(binaryMd, b);
dynamic bResult = readerB.attemptRead();
bool result = _compareInternal(aResult, bResult);
if (printOnExit) print(outputTo);
return result;
}
List<String> stack = [];
int outputLines = 0;
void printDifference(String s) {
outputTo!.writeln("----------");
outputTo!.writeln(s);
outputTo!.writeln("'Stacktrace':");
stack.forEach(outputTo!.writeln);
outputLines += 3 + stack.length;
}
bool _compareInternal(dynamic a, dynamic b) {
if (a.runtimeType != b.runtimeType) {
printDifference(
"Different runtime types (${a.runtimeType} and ${b.runtimeType})");
return false;
}
bool result = true;
if (a is List) {
List listA = a;
List listB = b;
int length = listA.length;
if (listA.length != listB.length) {
printDifference(
"Lists have different length (${listA.length} vs ${listB.length})");
result = false;
if (listB.length < listA.length) length = listB.length;
}
for (int i = 0; i < length; i++) {
stack.add("Lists at index $i ${_getTag(a)}");
if (!_compareInternal(listA[i], listB[i])) {
result = false;
}
stack.removeLast();
if (outputLines > 1000) return result;
}
return result;
}
if (a is Map<String, dynamic>) {
Map<String, dynamic> mapA = a;
Map<String, dynamic> mapB = b;
for (String key in mapA.keys) {
dynamic valueA = mapA[key];
dynamic valueB = mapB[key];
stack.add("Map with key '$key' ${_getTag(a)}");
if (!_compareInternal(valueA, valueB)) {
result = false;
}
stack.removeLast();
if (outputLines > 1000) return result;
}
if (mapA.length != mapB.length) {
printDifference("Maps have different number of entries "
"(${mapA.length} vs ${mapB.length}). ${_getTag(a)}");
result = false;
}
return result;
}
if (a is int) {
if (a != b) {
printDifference("Integers differ: $a vs $b");
return false;
}
return true;
}
throw "Unsupported: ${a.runtimeType}";
}
String _getTag(dynamic input) {
if (input is Map) {
dynamic tag = input["tag"];
if (tag != null) {
if (tagToName![tag] != null) {
return "(tag $tag, likely '${tagToName![tag]}')";
}
return "(tag $tag)";
}
}
return "";
}
}