|  | // 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 ""; | 
|  | } | 
|  | } |