// Copyright (c) 2016, 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. library kernel.ast_from_binary; import 'dart:convert'; import 'dart:typed_data'; import '../ast.dart'; import '../transformations/flags.dart'; import 'tag.dart'; class ParseError { String filename; int byteIndex; String message; String path; ParseError(this.message, {this.filename, this.byteIndex, this.path}); String toString() => '$filename:$byteIndex: $message at $path'; } class BinaryBuilder { final List variableStack = []; final List labelStack = []; int labelStackBase = 0; final List switchCaseStack = []; final List typeParameterStack = []; final String filename; final List _bytes; int _byteIndex = 0; final List _stringTable = []; final List _sourceUriTable = []; List _linkTable; int _transformerFlags = 0; Library _currentLibrary; // If something goes wrong, this list should indicate what library, // class, and member was being built. List debugPath = []; bool _isReadingLibraryImplementation = false; BinaryBuilder(this._bytes, [this.filename]); fail(String message) { throw new ParseError(message, byteIndex: _byteIndex, filename: filename, path: debugPath.join('::')); } int readByte() => _bytes[_byteIndex++]; int readUInt() { var byte = readByte(); if (byte & 0x80 == 0) { // 0xxxxxxx return byte; } else if (byte & 0x40 == 0) { // 10xxxxxx return ((byte & 0x3F) << 8) | readByte(); } else { // 11xxxxxx return ((byte & 0x3F) << 24) | (readByte() << 16) | (readByte() << 8) | readByte(); } } int readUint32() { return (readByte() << 24) | (readByte() << 16) | (readByte() << 8) | readByte(); } List readUtf8Bytes() { List bytes = new Uint8List(readUInt()); bytes.setRange(0, bytes.length, _bytes, _byteIndex); _byteIndex += bytes.length; return bytes; } String readStringEntry(int numBytes) { // Utf8Decoder will skip leading BOM characters, but we must preserve them. // Collect leading BOMs before passing the bytes onto Utf8Decoder. int numByteOrderMarks = 0; while (_byteIndex + 2 < _bytes.length && _bytes[_byteIndex] == 0xef && _bytes[_byteIndex + 1] == 0xbb && _bytes[_byteIndex + 2] == 0xbf) { ++numByteOrderMarks; _byteIndex += 3; numBytes -= 3; } String string = const Utf8Decoder().convert(_bytes, _byteIndex, _byteIndex + numBytes); _byteIndex += numBytes; if (numByteOrderMarks > 0) { return '\ufeff' * numByteOrderMarks + string; } return string; } void readStringTable(List table) { // Read the table of end offsets. int length = readUInt(); List endOffsets = new List(length); for (int i = 0; i < length; ++i) { endOffsets[i] = readUInt(); } // Read the UTF-8 encoded strings. table.length = length; int startOffset = 0; for (int i = 0; i < length; ++i) { table[i] = readStringEntry(endOffsets[i] - startOffset); startOffset = endOffsets[i]; } } String readUriReference() { return _sourceUriTable[readUInt()]; } String readStringReference() { return _stringTable[readUInt()]; } List readStringReferenceList() { return new List.generate(readUInt(), (i) => readStringReference()); } String readStringOrNullIfEmpty() { var string = readStringReference(); return string.isEmpty ? null : string; } bool readAndCheckOptionTag() { int tag = readByte(); if (tag == Tag.Nothing) { return false; } else if (tag == Tag.Something) { return true; } else { throw fail('Invalid Option tag: $tag'); } } List readAnnotationList(TreeNode parent) { int length = readUInt(); if (length == 0) return const []; List list = new List(length); for (int i = 0; i < length; ++i) { list[i] = readExpression()..parent = parent; } return list; } void _fillTreeNodeList( List list, TreeNode buildObject(), TreeNode parent) { var length = readUInt(); list.length = length; for (int i = 0; i < length; ++i) { TreeNode object = buildObject(); list[i] = object..parent = parent; } } void _fillNonTreeNodeList(List list, Node buildObject()) { var length = readUInt(); list.length = length; for (int i = 0; i < length; ++i) { Node object = buildObject(); list[i] = object; } } void _skipNodeList(Node skipObject()) { var length = readUInt(); for (int i = 0; i < length; ++i) { skipObject(); } } /// Reads a list of named nodes, reusing any existing objects already in the /// linking tree. The nodes are merged into [list], and if reading the library /// implementation, the order is corrected. /// /// [readObject] should read the object definition and its canonical name. /// If an existing object is bound to the canonical name, the existing object /// must be reused and returned. void _mergeNamedNodeList( List list, NamedNode readObject(), TreeNode parent) { if (_isReadingLibraryImplementation) { // When reading the library implementation, overwrite the whole list // with the new one. _fillTreeNodeList(list, readObject, parent); } else { // When reading an external library, the results should either be: // - merged with the existing external library definition (if any) // - ignored if the library implementation is already in memory int numberOfNodes = readUInt(); for (int i = 0; i < numberOfNodes; ++i) { var value = readObject(); // We use the parent pointer of a node to determine if it already is in // the AST and hence should not be added again. if (value.parent == null) { list.add(value..parent = parent); } } } } void readLinkTable(CanonicalName linkRoot) { int length = readUInt(); _linkTable = new List(length); for (int i = 0; i < length; ++i) { int biasedParentIndex = readUInt(); String name = readStringReference(); var parent = biasedParentIndex == 0 ? linkRoot : _linkTable[biasedParentIndex - 1]; _linkTable[i] = parent.getChild(name); } } /// Deserializes a kernel program and stores it in [program]. /// /// When linking with a non-empty program, canonical names must have been /// computed ahead of time. /// /// The input bytes may contain multiple files concatenated. void readProgram(Program program) { while (_byteIndex < _bytes.length) { _readOneProgram(program); } } /// Reads a single program file from the input and loads it into [program], /// overwriting and reusing any existing data in the program. /// /// When linking with a non-empty program, canonical names must have been /// computed ahead of time. /// /// This should *only* be used when there is a reason to not allow /// concatenated files. void readSingleFileProgram(Program program) { _readOneProgram(program); if (_byteIndex < _bytes.length) { if (_byteIndex + 3 < _bytes.length) { int magic = readUint32(); if (magic == Tag.ProgramFile) { throw 'Concatenated program file given when a single program ' 'was expected.'; } } throw 'Unrecognized bytes following program data'; } } void _readOneProgram(Program program) { int magic = readUint32(); if (magic != Tag.ProgramFile) { throw fail('This is not a binary dart file. ' 'Magic number was: ${magic.toRadixString(16)}'); } readStringTable(_stringTable); Map uriToSource = readUriToSource(); program.uriToSource.addAll(uriToSource); readLinkTable(program.root); int numberOfLibraries = readUInt(); List libraries = new List(numberOfLibraries); for (int i = 0; i < numberOfLibraries; ++i) { libraries[i] = readLibrary(program); } var mainMethod = readMemberReference(allowNull: true); program.mainMethodName ??= mainMethod; // Read the program index. readUint32(); // binary offset for source table. readUint32(); // binary offset for link table. readUint32(); // main for (int i = 0; i < numberOfLibraries; i++) { readUint32(); // binary offset for library #i. } int numberOfLibrariesCheck = readUint32(); if (numberOfLibraries != numberOfLibrariesCheck) { throw 'Malformed binary: the program index indicates there are ' '$numberOfLibrariesCheck libraries but the binary contains ' '$numberOfLibraries.'; } } Map readUriToSource() { readStringTable(_sourceUriTable); int length = _sourceUriTable.length; Map uriToSource = {}; for (int i = 0; i < length; ++i) { String uri = _sourceUriTable[i]; List sourceCode = readUtf8Bytes(); int lineCount = readUInt(); List lineStarts = new List(lineCount); int previousLineStart = 0; for (int j = 0; j < lineCount; ++j) { int lineStart = readUInt() + previousLineStart; lineStarts[j] = lineStart; previousLineStart = lineStart; } uriToSource[uri] = new Source(lineStarts, sourceCode); } return uriToSource; } CanonicalName readCanonicalNameReference() { var index = readUInt(); if (index == 0) return null; return _linkTable[index - 1]; } Reference readLibraryReference() { return readCanonicalNameReference().getReference(); } LibraryDependency readLibraryDependencyReference() { int index = readUInt(); return _currentLibrary.dependencies[index]; } Reference readClassReference({bool allowNull: false}) { var name = readCanonicalNameReference(); if (name == null && !allowNull) { throw 'Expected a class reference to be valid but was `null`.'; } return name?.getReference(); } Reference readMemberReference({bool allowNull: false}) { var name = readCanonicalNameReference(); if (name == null && !allowNull) { throw 'Expected a member reference to be valid but was `null`.'; } return name?.getReference(); } Reference readTypedefReference() { return readCanonicalNameReference()?.getReference(); } Name readName() { String text = readStringReference(); if (text.isNotEmpty && text[0] == '_') { return new Name.byReference(text, readLibraryReference()); } else { return new Name(text); } } Library readLibrary(Program program) { int flags = readByte(); bool isExternal = (flags & 0x1) != 0; _isReadingLibraryImplementation = !isExternal; var canonicalName = readCanonicalNameReference(); Reference reference = canonicalName.getReference(); Library library = reference.node; bool shouldWriteData = library == null || _isReadingLibraryImplementation; if (library == null) { library = new Library(Uri.parse(canonicalName.name), reference: reference); program.libraries.add(library..parent = program); } _currentLibrary = library; String name = readStringOrNullIfEmpty(); // TODO(jensj): We currently save (almost the same) uri twice. String fileUri = readUriReference(); if (shouldWriteData) { library.isExternal = isExternal; library.name = name; library.fileUri = fileUri; } debugPath.add(library.name ?? library.importUri?.toString() ?? 'library'); if (shouldWriteData) { _fillTreeNodeList(library.annotations, readExpression, library); } else { _skipNodeList(readExpression); } _readLibraryDependencies(library); _readLibraryParts(library); _mergeNamedNodeList(library.typedefs, readTypedef, library); _mergeNamedNodeList(library.classes, readClass, library); _mergeNamedNodeList(library.fields, readField, library); _mergeNamedNodeList(library.procedures, readProcedure, library); debugPath.removeLast(); _currentLibrary = null; return library; } void _readLibraryDependencies(Library library) { int length = readUInt(); if (library.isExternal) { assert(length == 0); return; } library.dependencies.length = length; for (int i = 0; i < length; ++i) { var flags = readByte(); var annotations = readExpressionList(); var targetLibrary = readLibraryReference(); var prefixName = readStringOrNullIfEmpty(); var names = readCombinatorList(); library.dependencies[i] = new LibraryDependency.byReference( flags, annotations, targetLibrary, prefixName, names) ..parent = library; } } Combinator readCombinator() { var isShow = readUInt() == 1; var names = readStringReferenceList(); return new Combinator(isShow, names); } List readCombinatorList() { return new List.generate(readUInt(), (i) => readCombinator()); } void _readLibraryParts(Library library) { int length = readUInt(); library.parts.length = length; for (int i = 0; i < length; ++i) { var annotations = readExpressionList(); var fileUri = readStringOrNullIfEmpty(); library.parts[i] = new LibraryPart(annotations, fileUri) ..parent = library; } } Typedef readTypedef() { var canonicalName = readCanonicalNameReference(); var reference = canonicalName.getReference(); Typedef node = reference.node; bool shouldWriteData = node == null || _isReadingLibraryImplementation; if (node == null) { node = new Typedef(null, null, reference: reference); } int fileOffset = readOffset(); String name = readStringReference(); String fileUri = readUriReference(); readAndPushTypeParameterList(node.typeParameters, node); var type = readDartType(); typeParameterStack.length = 0; if (shouldWriteData) { node.fileOffset = fileOffset; node.name = name; node.fileUri = fileUri; node.type = type; } return node; } Class readClass() { int tag = readByte(); assert(tag == Tag.Class); var canonicalName = readCanonicalNameReference(); var reference = canonicalName.getReference(); Class node = reference.node; bool shouldWriteData = node == null || _isReadingLibraryImplementation; if (node == null) { node = new Class(reference: reference)..level = ClassLevel.Temporary; } node.fileOffset = readOffset(); node.fileEndOffset = readOffset(); int flags = readByte(); node.isAbstract = flags & 0x1 != 0; node.isEnum = flags & 0x2 != 0; node.isSyntheticMixinImplementation = flags & 0x4 != 0; int levelIndex = (flags >> 3) & 0x3; var level = ClassLevel.values[levelIndex + 1]; if (level.index >= node.level.index) { node.level = level; } var name = readStringOrNullIfEmpty(); var fileUri = readUriReference(); var documentationComment = readStringOrNullIfEmpty(); var annotations = readAnnotationList(node); debugPath.add(node.name ?? 'normal-class'); readAndPushTypeParameterList(node.typeParameters, node); var supertype = readSupertypeOption(); var mixedInType = readSupertypeOption(); if (shouldWriteData) { _fillNonTreeNodeList(node.implementedTypes, readSupertype); } else { _skipNodeList(readSupertype); } _mergeNamedNodeList(node.fields, readField, node); _mergeNamedNodeList(node.constructors, readConstructor, node); _mergeNamedNodeList(node.procedures, readProcedure, node); typeParameterStack.length = 0; debugPath.removeLast(); if (shouldWriteData) { node.name = name; node.fileUri = fileUri; node.documentationComment = documentationComment; node.annotations = annotations; node.supertype = supertype; node.mixedInType = mixedInType; } return node; } int getAndResetTransformerFlags() { int flags = _transformerFlags; _transformerFlags = 0; return flags; } /// Adds the given flag to the current [Member.transformerFlags]. void addTransformerFlag(int flags) { _transformerFlags |= flags; } Field readField() { int tag = readByte(); assert(tag == Tag.Field); var canonicalName = readCanonicalNameReference(); var reference = canonicalName.getReference(); Field node = reference.node; bool shouldWriteData = node == null || _isReadingLibraryImplementation; if (node == null) { node = new Field(null, reference: reference); } int fileOffset = readOffset(); int fileEndOffset = readOffset(); int flags = readByte(); var name = readName(); var fileUri = readUriReference(); var documentationComment = readStringOrNullIfEmpty(); var annotations = readAnnotationList(node); debugPath.add(node.name?.name ?? 'field'); var type = readDartType(); var initializer = readExpressionOption(); int transformerFlags = getAndResetTransformerFlags(); debugPath.removeLast(); if (shouldWriteData) { node.fileOffset = fileOffset; node.fileEndOffset = fileEndOffset; node.flags = flags; node.name = name; node.fileUri = fileUri; node.documentationComment = documentationComment; node.annotations = annotations; node.type = type; node.initializer = initializer; node.initializer?.parent = node; node.transformerFlags = transformerFlags; } return node; } Constructor readConstructor() { int tag = readByte(); assert(tag == Tag.Constructor); var canonicalName = readCanonicalNameReference(); var reference = canonicalName.getReference(); Constructor node = reference.node; bool shouldWriteData = node == null || _isReadingLibraryImplementation; if (node == null) { node = new Constructor(null, reference: reference); } var fileOffset = readOffset(); var fileEndOffset = readOffset(); var flags = readByte(); var name = readName(); var documentationComment = readStringOrNullIfEmpty(); var annotations = readAnnotationList(node); debugPath.add(node.name?.name ?? 'constructor'); var function = readFunctionNode(); pushVariableDeclarations(function.positionalParameters); pushVariableDeclarations(function.namedParameters); if (shouldWriteData) { _fillTreeNodeList(node.initializers, readInitializer, node); } else { _skipNodeList(readInitializer); } variableStack.length = 0; var transformerFlags = getAndResetTransformerFlags(); debugPath.removeLast(); if (shouldWriteData) { node.fileOffset = fileOffset; node.fileEndOffset = fileEndOffset; node.flags = flags; node.name = name; node.documentationComment = documentationComment; node.annotations = annotations; node.function = function..parent = node; node.transformerFlags = transformerFlags; } return node; } Procedure readProcedure() { int tag = readByte(); assert(tag == Tag.Procedure); var canonicalName = readCanonicalNameReference(); var reference = canonicalName.getReference(); Procedure node = reference.node; bool shouldWriteData = node == null || _isReadingLibraryImplementation; if (node == null) { node = new Procedure(null, null, null, reference: reference); } var fileOffset = readOffset(); var fileEndOffset = readOffset(); int kindIndex = readByte(); var kind = ProcedureKind.values[kindIndex]; var flags = readByte(); var name = readName(); var fileUri = readUriReference(); var documentationComment = readStringOrNullIfEmpty(); var annotations = readAnnotationList(node); debugPath.add(node.name?.name ?? 'procedure'); var function = readFunctionNodeOption(); var transformerFlags = getAndResetTransformerFlags(); debugPath.removeLast(); if (shouldWriteData) { node.fileOffset = fileOffset; node.fileEndOffset = fileEndOffset; node.kind = kind; node.flags = flags; node.name = name; node.fileUri = fileUri; node.documentationComment = documentationComment; node.annotations = annotations; node.function = function; node.function?.parent = node; node.transformerFlags = transformerFlags; } return node; } Initializer readInitializer() { int tag = readByte(); bool isSynthetic = readByte() == 1; switch (tag) { case Tag.InvalidInitializer: return new InvalidInitializer(); case Tag.FieldInitializer: var reference = readMemberReference(); var value = readExpression(); return new FieldInitializer.byReference(reference, value) ..isSynthetic = isSynthetic; case Tag.SuperInitializer: var reference = readMemberReference(); var arguments = readArguments(); return new SuperInitializer.byReference(reference, arguments) ..isSynthetic = isSynthetic; case Tag.RedirectingInitializer: return new RedirectingInitializer.byReference( readMemberReference(), readArguments()); case Tag.LocalInitializer: return new LocalInitializer(readAndPushVariableDeclaration()); default: throw fail('Invalid initializer tag: $tag'); } } FunctionNode readFunctionNodeOption() { return readAndCheckOptionTag() ? readFunctionNode() : null; } FunctionNode readFunctionNode() { int tag = readByte(); assert(tag == Tag.FunctionNode); int offset = readOffset(); int endOffset = readOffset(); AsyncMarker asyncMarker = AsyncMarker.values[readByte()]; AsyncMarker dartAsyncMarker = AsyncMarker.values[readByte()]; int typeParameterStackHeight = typeParameterStack.length; var typeParameters = readAndPushTypeParameterList(); readUInt(); // total parameter count. var requiredParameterCount = readUInt(); int variableStackHeight = variableStack.length; var positional = readAndPushVariableDeclarationList(); var named = readAndPushVariableDeclarationList(); var returnType = readDartType(); int oldLabelStackBase = labelStackBase; labelStackBase = labelStack.length; var body = readStatementOption(); labelStackBase = oldLabelStackBase; variableStack.length = variableStackHeight; typeParameterStack.length = typeParameterStackHeight; return new FunctionNode(body, typeParameters: typeParameters, requiredParameterCount: requiredParameterCount, positionalParameters: positional, namedParameters: named, returnType: returnType, asyncMarker: asyncMarker, dartAsyncMarker: dartAsyncMarker) ..fileOffset = offset ..fileEndOffset = endOffset; } void pushVariableDeclaration(VariableDeclaration variable) { variableStack.add(variable); } void pushVariableDeclarations(List variables) { variableStack.addAll(variables); } VariableDeclaration readVariableReference() { int index = readUInt(); if (index >= variableStack.length) { throw fail('Invalid variable index: $index'); } return variableStack[index]; } String logicalOperatorToString(int index) { switch (index) { case 0: return '&&'; case 1: return '||'; default: throw fail('Invalid logical operator index: $index'); } } List readExpressionList() { return new List.generate(readUInt(), (i) => readExpression()); } Expression readExpressionOption() { return readAndCheckOptionTag() ? readExpression() : null; } Expression readExpression() { int tagByte = readByte(); int tag = tagByte & Tag.SpecializedTagHighBit == 0 ? tagByte : (tagByte & Tag.SpecializedTagMask); switch (tag) { case Tag.LoadLibrary: return new LoadLibrary(readLibraryDependencyReference()); case Tag.CheckLibraryIsLoaded: return new CheckLibraryIsLoaded(readLibraryDependencyReference()); case Tag.InvalidExpression: return new InvalidExpression(); case Tag.VariableGet: int offset = readOffset(); readUInt(); // offset of the variable declaration in the binary. return new VariableGet(readVariableReference(), readDartTypeOption()) ..fileOffset = offset; case Tag.SpecializedVariableGet: int index = tagByte & Tag.SpecializedPayloadMask; int offset = readOffset(); readUInt(); // offset of the variable declaration in the binary. return new VariableGet(variableStack[index])..fileOffset = offset; case Tag.VariableSet: int offset = readOffset(); readUInt(); // offset of the variable declaration in the binary. return new VariableSet(readVariableReference(), readExpression()) ..fileOffset = offset; case Tag.SpecializedVariableSet: int index = tagByte & Tag.SpecializedPayloadMask; int offset = readOffset(); readUInt(); // offset of the variable declaration in the binary. return new VariableSet(variableStack[index], readExpression()) ..fileOffset = offset; case Tag.PropertyGet: int offset = readOffset(); return new PropertyGet.byReference( readExpression(), readName(), readMemberReference(allowNull: true)) ..fileOffset = offset; case Tag.PropertySet: int offset = readOffset(); return new PropertySet.byReference(readExpression(), readName(), readExpression(), readMemberReference(allowNull: true)) ..fileOffset = offset; case Tag.SuperPropertyGet: addTransformerFlag(TransformerFlag.superCalls); return new SuperPropertyGet.byReference( readName(), readMemberReference(allowNull: true)); case Tag.SuperPropertySet: addTransformerFlag(TransformerFlag.superCalls); return new SuperPropertySet.byReference( readName(), readExpression(), readMemberReference(allowNull: true)); case Tag.DirectPropertyGet: int offset = readOffset(); return new DirectPropertyGet.byReference( readExpression(), readMemberReference()) ..fileOffset = offset; case Tag.DirectPropertySet: int offset = readOffset(); return new DirectPropertySet.byReference( readExpression(), readMemberReference(), readExpression()) ..fileOffset = offset; case Tag.StaticGet: int offset = readOffset(); return new StaticGet.byReference(readMemberReference()) ..fileOffset = offset; case Tag.StaticSet: int offset = readOffset(); return new StaticSet.byReference( readMemberReference(), readExpression()) ..fileOffset = offset; case Tag.MethodInvocation: int offset = readOffset(); return new MethodInvocation.byReference(readExpression(), readName(), readArguments(), readMemberReference(allowNull: true)) ..fileOffset = offset; case Tag.SuperMethodInvocation: int offset = readOffset(); addTransformerFlag(TransformerFlag.superCalls); return new SuperMethodInvocation.byReference( readName(), readArguments(), readMemberReference(allowNull: true)) ..fileOffset = offset; case Tag.DirectMethodInvocation: return new DirectMethodInvocation.byReference( readExpression(), readMemberReference(), readArguments()); case Tag.StaticInvocation: int offset = readOffset(); return new StaticInvocation.byReference( readMemberReference(), readArguments(), isConst: false) ..fileOffset = offset; case Tag.ConstStaticInvocation: int offset = readOffset(); return new StaticInvocation.byReference( readMemberReference(), readArguments(), isConst: true) ..fileOffset = offset; case Tag.ConstructorInvocation: int offset = readOffset(); return new ConstructorInvocation.byReference( readMemberReference(), readArguments(), isConst: false) ..fileOffset = offset; case Tag.ConstConstructorInvocation: int offset = readOffset(); return new ConstructorInvocation.byReference( readMemberReference(), readArguments(), isConst: true) ..fileOffset = offset; case Tag.Not: return new Not(readExpression()); case Tag.LogicalExpression: return new LogicalExpression(readExpression(), logicalOperatorToString(readByte()), readExpression()); case Tag.ConditionalExpression: return new ConditionalExpression(readExpression(), readExpression(), readExpression(), readDartTypeOption()); case Tag.StringConcatenation: int offset = readOffset(); return new StringConcatenation(readExpressionList()) ..fileOffset = offset; case Tag.IsExpression: int offset = readOffset(); return new IsExpression(readExpression(), readDartType()) ..fileOffset = offset; case Tag.AsExpression: int offset = readOffset(); return new AsExpression(readExpression(), readDartType()) ..fileOffset = offset; case Tag.StringLiteral: return new StringLiteral(readStringReference()); case Tag.SpecializedIntLiteral: int biasedValue = tagByte & Tag.SpecializedPayloadMask; return new IntLiteral(biasedValue - Tag.SpecializedIntLiteralBias); case Tag.PositiveIntLiteral: return new IntLiteral(readUInt()); case Tag.NegativeIntLiteral: return new IntLiteral(-readUInt()); case Tag.BigIntLiteral: return new IntLiteral(int.parse(readStringReference())); case Tag.DoubleLiteral: return new DoubleLiteral(double.parse(readStringReference())); case Tag.TrueLiteral: return new BoolLiteral(true); case Tag.FalseLiteral: return new BoolLiteral(false); case Tag.NullLiteral: return new NullLiteral(); case Tag.SymbolLiteral: return new SymbolLiteral(readStringReference()); case Tag.TypeLiteral: return new TypeLiteral(readDartType()); case Tag.ThisExpression: return new ThisExpression(); case Tag.Rethrow: int offset = readOffset(); return new Rethrow()..fileOffset = offset; case Tag.Throw: int offset = readOffset(); return new Throw(readExpression())..fileOffset = offset; case Tag.ListLiteral: int offset = readOffset(); var typeArgument = readDartType(); return new ListLiteral(readExpressionList(), typeArgument: typeArgument, isConst: false) ..fileOffset = offset; case Tag.ConstListLiteral: int offset = readOffset(); var typeArgument = readDartType(); return new ListLiteral(readExpressionList(), typeArgument: typeArgument, isConst: true) ..fileOffset = offset; case Tag.MapLiteral: int offset = readOffset(); var keyType = readDartType(); var valueType = readDartType(); return new MapLiteral(readMapEntryList(), keyType: keyType, valueType: valueType, isConst: false) ..fileOffset = offset; case Tag.ConstMapLiteral: int offset = readOffset(); var keyType = readDartType(); var valueType = readDartType(); return new MapLiteral(readMapEntryList(), keyType: keyType, valueType: valueType, isConst: true) ..fileOffset = offset; case Tag.AwaitExpression: return new AwaitExpression(readExpression()); case Tag.FunctionExpression: return new FunctionExpression(readFunctionNode()); case Tag.Let: var variable = readVariableDeclaration(); int stackHeight = variableStack.length; pushVariableDeclaration(variable); var body = readExpression(); variableStack.length = stackHeight; return new Let(variable, body); case Tag.VectorCreation: var length = readUInt(); return new VectorCreation(length); case Tag.VectorGet: var vectorExpression = readExpression(); var index = readUInt(); return new VectorGet(vectorExpression, index); case Tag.VectorSet: var vectorExpression = readExpression(); var index = readUInt(); var value = readExpression(); return new VectorSet(vectorExpression, index, value); case Tag.VectorCopy: var vectorExpression = readExpression(); return new VectorCopy(vectorExpression); case Tag.ClosureCreation: var topLevelFunctionReference = readMemberReference(); var contextVector = readExpression(); var functionType = readDartType(); var typeArgs = readDartTypeList(); return new ClosureCreation.byReference( topLevelFunctionReference, contextVector, functionType, typeArgs); default: throw fail('Invalid expression tag: $tag'); } } List readMapEntryList() { return new List.generate(readUInt(), (i) => readMapEntry()); } MapEntry readMapEntry() { return new MapEntry(readExpression(), readExpression()); } List readStatementList() { return new List.generate(readUInt(), (i) => readStatement()); } Statement readStatementOrNullIfEmpty() { var node = readStatement(); if (node is EmptyStatement) { return null; } else { return node; } } Statement readStatementOption() { return readAndCheckOptionTag() ? readStatement() : null; } Statement readStatement() { int tag = readByte(); switch (tag) { case Tag.InvalidStatement: return new InvalidStatement(); case Tag.ExpressionStatement: return new ExpressionStatement(readExpression()); case Tag.Block: return readBlock(); case Tag.EmptyStatement: return new EmptyStatement(); case Tag.AssertStatement: return new AssertStatement(readExpression(), conditionStartOffset: readOffset(), conditionEndOffset: readOffset(), message: readExpressionOption()); case Tag.LabeledStatement: var label = new LabeledStatement(null); labelStack.add(label); label.body = readStatement()..parent = label; labelStack.removeLast(); return label; case Tag.BreakStatement: int offset = readOffset(); int index = readUInt(); return new BreakStatement(labelStack[labelStackBase + index]) ..fileOffset = offset; case Tag.WhileStatement: var offset = readOffset(); return new WhileStatement(readExpression(), readStatement()) ..fileOffset = offset; case Tag.DoStatement: var offset = readOffset(); return new DoStatement(readStatement(), readExpression()) ..fileOffset = offset; case Tag.ForStatement: int variableStackHeight = variableStack.length; var offset = readOffset(); var variables = readAndPushVariableDeclarationList(); var condition = readExpressionOption(); var updates = readExpressionList(); var body = readStatement(); variableStack.length = variableStackHeight; return new ForStatement(variables, condition, updates, body) ..fileOffset = offset; case Tag.ForInStatement: case Tag.AsyncForInStatement: bool isAsync = tag == Tag.AsyncForInStatement; int variableStackHeight = variableStack.length; var offset = readOffset(); var bodyOffset = readOffset(); var variable = readAndPushVariableDeclaration(); var iterable = readExpression(); var body = readStatement(); variableStack.length = variableStackHeight; return new ForInStatement(variable, iterable, body, isAsync: isAsync) ..fileOffset = offset ..bodyOffset = bodyOffset; case Tag.SwitchStatement: var expression = readExpression(); int count = readUInt(); List cases = new List.generate(count, (i) => new SwitchCase.empty()); switchCaseStack.addAll(cases); for (int i = 0; i < cases.length; ++i) { var caseNode = cases[i]; int length = readUInt(); caseNode.expressions.length = length; caseNode.expressionOffsets.length = length; for (int i = 0; i < length; ++i) { caseNode.expressionOffsets[i] = readOffset(); caseNode.expressions[i] = readExpression()..parent = caseNode; } caseNode.isDefault = readByte() == 1; caseNode.body = readStatement()..parent = caseNode; } switchCaseStack.length -= count; return new SwitchStatement(expression, cases); case Tag.ContinueSwitchStatement: int index = readUInt(); return new ContinueSwitchStatement(switchCaseStack[index]); case Tag.IfStatement: return new IfStatement( readExpression(), readStatement(), readStatementOrNullIfEmpty()); case Tag.ReturnStatement: int offset = readOffset(); return new ReturnStatement(readExpressionOption())..fileOffset = offset; case Tag.TryCatch: Statement body = readStatement(); readByte(); // whether any catch needs a stacktrace. return new TryCatch(body, readCatchList()); case Tag.TryFinally: return new TryFinally(readStatement(), readStatement()); case Tag.YieldStatement: int offset = readOffset(); int flags = readByte(); return new YieldStatement(readExpression(), isYieldStar: flags & YieldStatement.FlagYieldStar != 0, isNative: flags & YieldStatement.FlagNative != 0) ..fileOffset = offset; case Tag.VariableDeclaration: var variable = readVariableDeclaration(); variableStack.add(variable); // Will be popped by the enclosing scope. return variable; case Tag.FunctionDeclaration: int offset = readOffset(); var variable = readVariableDeclaration(); variableStack.add(variable); // Will be popped by the enclosing scope. var function = readFunctionNode(); return new FunctionDeclaration(variable, function)..fileOffset = offset; default: throw fail('Invalid statement tag: $tag'); } } List readCatchList() { return new List.generate(readUInt(), (i) => readCatch()); } Catch readCatch() { int variableStackHeight = variableStack.length; var guard = readDartType(); var exception = readAndPushVariableDeclarationOption(); var stackTrace = readAndPushVariableDeclarationOption(); var body = readStatement(); variableStack.length = variableStackHeight; return new Catch(exception, body, guard: guard, stackTrace: stackTrace); } Block readBlock() { int stackHeight = variableStack.length; var body = readStatementList(); variableStack.length = stackHeight; return new Block(body); } Supertype readSupertype() { InterfaceType type = readDartType(); return new Supertype.byReference(type.className, type.typeArguments); } Supertype readSupertypeOption() { return readAndCheckOptionTag() ? readSupertype() : null; } List readSupertypeList() { return new List.generate(readUInt(), (i) => readSupertype()); } List readDartTypeList() { return new List.generate(readUInt(), (i) => readDartType()); } List readNamedTypeList() { return new List.generate(readUInt(), (i) => readNamedType()); } NamedType readNamedType() { return new NamedType(readStringReference(), readDartType()); } DartType readDartTypeOption() { return readAndCheckOptionTag() ? readDartType() : null; } DartType readDartType() { int tag = readByte(); switch (tag) { case Tag.TypedefType: return new TypedefType.byReference( readTypedefReference(), readDartTypeList()); case Tag.VectorType: return const VectorType(); case Tag.BottomType: return const BottomType(); case Tag.InvalidType: return const InvalidType(); case Tag.DynamicType: return const DynamicType(); case Tag.VoidType: return const VoidType(); case Tag.InterfaceType: return new InterfaceType.byReference( readClassReference(), readDartTypeList()); case Tag.SimpleInterfaceType: return new InterfaceType.byReference( readClassReference(), const []); case Tag.FunctionType: int typeParameterStackHeight = typeParameterStack.length; var typeParameters = readAndPushTypeParameterList(); var requiredParameterCount = readUInt(); var totalParameterCount = readUInt(); var positional = readDartTypeList(); var named = readNamedTypeList(); var positionalNames = readStringReferenceList(); var typedefReference = readTypedefReference(); assert(positional.length + named.length == totalParameterCount); var returnType = readDartType(); typeParameterStack.length = typeParameterStackHeight; return new FunctionType(positional, returnType, typeParameters: typeParameters, requiredParameterCount: requiredParameterCount, namedParameters: named, positionalParameterNames: positionalNames, typedefReference: typedefReference); case Tag.SimpleFunctionType: var positional = readDartTypeList(); var positionalNames = readStringReferenceList(); var returnType = readDartType(); return new FunctionType(positional, returnType, positionalParameterNames: positionalNames); case Tag.TypeParameterType: int index = readUInt(); var bound = readDartTypeOption(); return new TypeParameterType(typeParameterStack[index], bound); default: throw fail('Invalid dart type tag: $tag'); } } List readAndPushTypeParameterList( [List list, TreeNode parent]) { int length = readUInt(); if (length == 0) return list ?? []; if (list == null) { list = new List.generate( length, (i) => new TypeParameter(null, null)..parent = parent); } else if (list.length != length) { list.length = length; for (int i = 0; i < length; ++i) { list[i] = new TypeParameter(null, null)..parent = parent; } } typeParameterStack.addAll(list); for (int i = 0; i < list.length; ++i) { readTypeParameter(list[i]); } return list; } void readTypeParameter(TypeParameter node) { node.name = readStringOrNullIfEmpty(); node.bound = readDartType(); } Arguments readArguments() { var numArguments = readUInt(); var typeArguments = readDartTypeList(); var positional = readExpressionList(); var named = readNamedExpressionList(); assert(numArguments == positional.length + named.length); return new Arguments(positional, types: typeArguments, named: named); } List readNamedExpressionList() { return new List.generate( readUInt(), (i) => readNamedExpression()); } NamedExpression readNamedExpression() { return new NamedExpression(readStringReference(), readExpression()); } List readAndPushVariableDeclarationList() { return new List.generate( readUInt(), (i) => readAndPushVariableDeclaration()); } VariableDeclaration readAndPushVariableDeclarationOption() { return readAndCheckOptionTag() ? readAndPushVariableDeclaration() : null; } VariableDeclaration readAndPushVariableDeclaration() { var variable = readVariableDeclaration(); variableStack.add(variable); return variable; } VariableDeclaration readVariableDeclaration() { int offset = readOffset(); int fileEqualsOffset = readOffset(); int flags = readByte(); return new VariableDeclaration(readStringOrNullIfEmpty(), type: readDartType(), initializer: readExpressionOption(), isFinal: flags & 0x1 != 0, isConst: flags & 0x2 != 0) ..fileOffset = offset ..fileEqualsOffset = fileEqualsOffset; } int readOffset() { // Offset is saved as unsigned, // but actually ranges from -1 and up (thus the -1) return readUInt() - 1; } }