Add binary serialization
diff --git a/lib/binary_codec.dart b/lib/binary_codec.dart
new file mode 100644
index 0000000..b739114
--- /dev/null
+++ b/lib/binary_codec.dart
@@ -0,0 +1,458 @@
+// Copyright (c) 2015, 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.
+
+/// Info serialization to a binary form.
+///
+/// Unlike the JSON codec, this serialization is designed to be streamed.
+
+import 'dart:convert';
+
+import 'src/binary/sink.dart';
+import 'src/binary/source.dart';
+import 'info.dart';
+
+void encode(AllInfo info, Sink<List<int>> sink) {
+ new BinaryPrinter(new BinarySink(sink)).visitAll(info);
+}
+
+AllInfo decode(List<int> data) {
+ return new BinaryReader(new BinarySource(data)).readAll();
+}
+
+class BinaryPrinter implements InfoVisitor<void> {
+ final BinarySink sink;
+
+ BinaryPrinter(this.sink);
+
+ void writeDate(DateTime date) {
+ sink.writeString(date.toIso8601String());
+ }
+
+ void writeDuration(Duration duration) {
+ sink.writeInt(duration.inMicroseconds);
+ }
+
+ void writeInfoWithKind(Info info) {
+ sink.writeEnum(info.kind);
+ info.accept(this);
+ }
+
+ void visitAll(AllInfo info) {
+ sink.writeInt(info.version);
+ sink.writeInt(info.minorVersion);
+ sink.writeList(info.libraries, visitLibrary);
+ // TODO(sigmund): synthesize the following lists instead of serializing the
+ // values again.
+ sink.writeList(info.classes, visitClass);
+ sink.writeList(info.functions, visitFunction);
+ sink.writeList(info.typedefs, visitTypedef);
+ sink.writeList(info.fields, visitField);
+ sink.writeList(info.constants, visitConstant);
+ sink.writeList(info.closures, visitClosure);
+
+ void writeDependencies(CodeInfo info) {
+ sink.writeList(info.uses, _writeDependencyInfo);
+ }
+
+ info.fields.forEach(writeDependencies);
+ info.functions.forEach(writeDependencies);
+
+ sink.writeInt(info.dependencies.length);
+ info.dependencies.forEach((Info key, List<Info> values) {
+ writeInfoWithKind(key);
+ sink.writeList(values, writeInfoWithKind);
+ });
+ sink.writeList(info.outputUnits, visitOutput);
+ sink.writeString(jsonEncode(info.deferredFiles));
+ visitProgram(info.program);
+ sink.close();
+ }
+
+ void visitProgram(ProgramInfo info) {
+ visitFunction(info.entrypoint);
+ sink.writeInt(info.size);
+ sink.writeString(info.dart2jsVersion);
+ writeDate(info.compilationMoment);
+ writeDuration(info.compilationDuration);
+ // Note: we don't record the 'toJsonDuration' field. Consider deleting it?
+ writeDuration(info.dumpInfoDuration);
+ sink.writeBool(info.noSuchMethodEnabled);
+ sink.writeBool(info.isRuntimeTypeUsed);
+ sink.writeBool(info.isIsolateInUse);
+ sink.writeBool(info.isFunctionApplyUsed);
+ sink.writeBool(info.isMirrorsUsed);
+ sink.writeBool(info.minified);
+ }
+
+ void _visitBasicInfo(BasicInfo info) {
+ sink.writeStringOrNull(info.name);
+ sink.writeInt(info.size);
+ sink.writeStringOrNull(info.coverageId);
+ _writeOutputOrNull(info.outputUnit);
+ // Note: parent-pointers are not serialized, they get deduced during deserialization.
+ }
+
+ void visitLibrary(LibraryInfo library) {
+ sink.writeCached(library, (LibraryInfo info) {
+ sink.writeUri(info.uri);
+ _visitBasicInfo(info);
+ sink.writeList(info.topLevelFunctions, visitFunction);
+ sink.writeList(info.topLevelVariables, visitField);
+ sink.writeList(info.classes, visitClass);
+ sink.writeList(info.typedefs, visitTypedef);
+ });
+ }
+
+ void visitClass(ClassInfo cls) {
+ sink.writeCached(cls, (ClassInfo info) {
+ _visitBasicInfo(info);
+ sink.writeBool(info.isAbstract);
+ sink.writeList(info.fields, visitField);
+ sink.writeList(info.functions, visitFunction);
+ });
+ }
+
+ void visitField(FieldInfo field) {
+ sink.writeCached(field, (FieldInfo info) {
+ _visitBasicInfo(info);
+ sink.writeList(info.closures, visitClosure);
+ sink.writeString(info.inferredType);
+ sink.writeList(info.code, _visitCodeSpan);
+ sink.writeString(info.type);
+ sink.writeBool(info.isConst);
+ if (info.isConst) {
+ _writeConstantOrNull(info.initializer);
+ }
+ });
+ }
+
+ _visitCodeSpan(CodeSpan code) {
+ _writeOutputOrNull(code.outputUnit);
+ sink.writeIntOrNull(code.start);
+ sink.writeIntOrNull(code.end);
+ sink.writeStringOrNull(code.text);
+ }
+
+ void _writeConstantOrNull(ConstantInfo info) {
+ sink.writeBool(info != null);
+ if (info != null) {
+ visitConstant(info);
+ }
+ }
+
+ void visitConstant(ConstantInfo constant) {
+ sink.writeCached(constant, (ConstantInfo info) {
+ _visitBasicInfo(info);
+ sink.writeList(info.code, _visitCodeSpan);
+ });
+ }
+
+ void _visitFunctionModifiers(FunctionModifiers mods) {
+ int value = 0;
+ if (mods.isStatic) value |= _staticMask;
+ if (mods.isConst) value |= _constMask;
+ if (mods.isFactory) value |= _factoryMask;
+ if (mods.isExternal) value |= _externalMask;
+ sink.writeInt(value);
+ }
+
+ void _visitParameterInfo(ParameterInfo info) {
+ sink.writeString(info.name);
+ sink.writeString(info.type);
+ sink.writeString(info.declaredType);
+ }
+
+ void visitFunction(FunctionInfo function) {
+ sink.writeCached(function, (FunctionInfo info) {
+ _visitBasicInfo(info);
+ sink.writeList(info.closures, visitClosure);
+ _visitFunctionModifiers(info.modifiers);
+ sink.writeString(info.returnType);
+ sink.writeString(info.inferredReturnType);
+ sink.writeList(info.parameters, _visitParameterInfo);
+ sink.writeString(info.sideEffects);
+ sink.writeIntOrNull(info.inlinedCount);
+ sink.writeList(info.code, _visitCodeSpan);
+ sink.writeString(info.type);
+ });
+ }
+
+ void _writeDependencyInfo(DependencyInfo info) {
+ writeInfoWithKind(info.target);
+ sink.writeStringOrNull(info.mask);
+ }
+
+ void visitClosure(ClosureInfo closure) {
+ sink.writeCached(closure, (ClosureInfo info) {
+ _visitBasicInfo(info);
+ visitFunction(info.function);
+ });
+ }
+
+ void visitTypedef(TypedefInfo typedef) {
+ sink.writeCached(typedef, (TypedefInfo info) {
+ _visitBasicInfo(info);
+ sink.writeString(info.type);
+ });
+ }
+
+ void _writeOutputOrNull(OutputUnitInfo info) {
+ sink.writeBool(info != null);
+ if (info != null) {
+ visitOutput(info);
+ }
+ }
+
+ void visitOutput(OutputUnitInfo output) {
+ sink.writeCached(output, (OutputUnitInfo info) {
+ _visitBasicInfo(info);
+ sink.writeList(info.imports, sink.writeString);
+ });
+ }
+}
+
+class BinaryReader {
+ final BinarySource source;
+ BinaryReader(this.source);
+
+ DateTime readDate() {
+ return DateTime.parse(source.readString());
+ }
+
+ Duration readDuration() {
+ return new Duration(microseconds: source.readInt());
+ }
+
+ Info readInfoWithKind() {
+ InfoKind kind = source.readEnum(InfoKind.values);
+ switch (kind) {
+ case InfoKind.library:
+ return readLibrary();
+ case InfoKind.clazz:
+ return readClass();
+ case InfoKind.function:
+ return readFunction();
+ case InfoKind.field:
+ return readField();
+ case InfoKind.constant:
+ return readConstant();
+ case InfoKind.outputUnit:
+ return readOutput();
+ case InfoKind.typedef:
+ return readTypedef();
+ case InfoKind.closure:
+ return readClosure();
+ }
+ return null;
+ }
+
+ AllInfo readAll() {
+ var info = new AllInfo();
+ int version = source.readInt();
+ int minorVersion = source.readInt();
+ if (info.version != version || info.minorVersion != minorVersion) {
+ print("warning: data was encoded with format version "
+ "$version.$minorVersion, but decoded with "
+ "${info.version}.${info.minorVersion}");
+ }
+ info.libraries = source.readList(readLibrary);
+ info.classes = source.readList(readClass);
+ info.functions = source.readList(readFunction);
+ info.typedefs = source.readList(readTypedef);
+ info.fields = source.readList(readField);
+ info.constants = source.readList(readConstant);
+ info.closures = source.readList(readClosure);
+
+ void readDependencies(CodeInfo info) {
+ info.uses = source.readList(_readDependencyInfo);
+ }
+
+ info.fields.forEach(readDependencies);
+ info.functions.forEach(readDependencies);
+
+ int dependenciesTotal = source.readInt();
+ while (dependenciesTotal > 0) {
+ Info key = readInfoWithKind();
+ List<Info> values = source.readList(readInfoWithKind);
+ info.dependencies[key] = values;
+ dependenciesTotal--;
+ }
+
+ info.outputUnits = source.readList(readOutput);
+
+ Map<String, Map<String, dynamic>> map =
+ jsonDecode(source.readString()).cast<String, Map<String, dynamic>>();
+ for (final library in map.values) {
+ if (library['imports'] != null) {
+ // The importMap needs to be typed as <String, List<String>>, but the
+ // json parser produces <String, dynamic>.
+ final importMap = library['imports'] as Map<String, dynamic>;
+ importMap.forEach((prefix, files) {
+ importMap[prefix] = (files as List<dynamic>).cast<String>();
+ });
+ library['imports'] = importMap.cast<String, List<String>>();
+ }
+ }
+ info.deferredFiles = map;
+ info.program = readProgram();
+ return info;
+ }
+
+ ProgramInfo readProgram() {
+ var info = new ProgramInfo();
+ info.entrypoint = readFunction();
+ info.size = source.readInt();
+ info.dart2jsVersion = source.readString();
+ info.compilationMoment = readDate();
+ info.compilationDuration = readDuration();
+ info.toJsonDuration = new Duration(microseconds: 0);
+ info.dumpInfoDuration = readDuration();
+ info.noSuchMethodEnabled = source.readBool();
+ info.isRuntimeTypeUsed = source.readBool();
+ info.isIsolateInUse = source.readBool();
+ info.isFunctionApplyUsed = source.readBool();
+ info.isMirrorsUsed = source.readBool();
+ info.minified = source.readBool();
+ return info;
+ }
+
+ void _readBasicInfo(BasicInfo info) {
+ info.name = source.readStringOrNull();
+ info.size = source.readInt();
+ info.coverageId = source.readStringOrNull();
+ info.outputUnit = _readOutputOrNull();
+ // Note: parent pointers are added when deserializing parent nodes.
+ }
+
+ LibraryInfo readLibrary() => source.readCached<LibraryInfo>(() {
+ LibraryInfo info = new LibraryInfo.internal();
+ info.uri = source.readUri();
+ _readBasicInfo(info);
+ info.topLevelFunctions = source.readList(readFunction);
+ info.topLevelVariables = source.readList(readField);
+ info.classes = source.readList(readClass);
+ info.typedefs = source.readList(readTypedef);
+
+ setParent(BasicInfo child) => child.parent = info;
+ info.topLevelFunctions.forEach(setParent);
+ info.topLevelVariables.forEach(setParent);
+ info.classes.forEach(setParent);
+ info.typedefs.forEach(setParent);
+ return info;
+ });
+
+ ClassInfo readClass() => source.readCached<ClassInfo>(() {
+ ClassInfo info = new ClassInfo.internal();
+ _readBasicInfo(info);
+ info.isAbstract = source.readBool();
+ info.fields = source.readList(readField);
+ info.functions = source.readList(readFunction);
+
+ setParent(BasicInfo child) => child.parent = info;
+ info.fields.forEach(setParent);
+ info.functions.forEach(setParent);
+ return info;
+ });
+
+ FieldInfo readField() => source.readCached<FieldInfo>(() {
+ FieldInfo info = new FieldInfo.internal();
+ _readBasicInfo(info);
+ info.closures = source.readList(readClosure);
+ info.inferredType = source.readString();
+ info.code = source.readList(_readCodeSpan);
+ info.type = source.readString();
+ info.isConst = source.readBool();
+ if (info.isConst) {
+ info.initializer = _readConstantOrNull();
+ }
+ info.closures.forEach((c) => c.parent = info);
+ return info;
+ });
+
+ CodeSpan _readCodeSpan() {
+ return new CodeSpan()
+ ..outputUnit = _readOutputOrNull()
+ ..start = source.readIntOrNull()
+ ..end = source.readIntOrNull()
+ ..text = source.readStringOrNull();
+ }
+
+ ConstantInfo _readConstantOrNull() {
+ bool hasOutput = source.readBool();
+ if (hasOutput) return readConstant();
+ return null;
+ }
+
+ ConstantInfo readConstant() => source.readCached<ConstantInfo>(() {
+ ConstantInfo info = new ConstantInfo.internal();
+ _readBasicInfo(info);
+ info.code = source.readList(_readCodeSpan);
+ return info;
+ });
+
+ FunctionModifiers _readFunctionModifiers() {
+ int value = source.readInt();
+ return new FunctionModifiers(
+ isStatic: value & _staticMask != 0,
+ isConst: value & _constMask != 0,
+ isFactory: value & _factoryMask != 0,
+ isExternal: value & _externalMask != 0);
+ }
+
+ ParameterInfo _readParameterInfo() {
+ return new ParameterInfo(
+ source.readString(), source.readString(), source.readString());
+ }
+
+ FunctionInfo readFunction() => source.readCached<FunctionInfo>(() {
+ FunctionInfo info = new FunctionInfo.internal();
+ _readBasicInfo(info);
+ info.closures = source.readList(readClosure);
+ info.modifiers = _readFunctionModifiers();
+ info.returnType = source.readString();
+ info.inferredReturnType = source.readString();
+ info.parameters = source.readList(_readParameterInfo);
+ info.sideEffects = source.readString();
+ info.inlinedCount = source.readIntOrNull();
+ info.code = source.readList(_readCodeSpan);
+ info.type = source.readString();
+ info.closures.forEach((c) => c.parent = info);
+ return info;
+ });
+
+ DependencyInfo _readDependencyInfo() =>
+ new DependencyInfo(readInfoWithKind(), source.readStringOrNull());
+
+ ClosureInfo readClosure() => source.readCached<ClosureInfo>(() {
+ ClosureInfo info = new ClosureInfo.internal();
+ _readBasicInfo(info);
+ info.function = readFunction();
+ info.function.parent = info;
+ return info;
+ });
+
+ TypedefInfo readTypedef() => source.readCached<TypedefInfo>(() {
+ TypedefInfo info = new TypedefInfo.internal();
+ _readBasicInfo(info);
+ info.type = source.readString();
+ });
+
+ OutputUnitInfo _readOutputOrNull() {
+ bool hasOutput = source.readBool();
+ if (hasOutput) return readOutput();
+ return null;
+ }
+
+ OutputUnitInfo readOutput() => source.readCached<OutputUnitInfo>(() {
+ OutputUnitInfo info = new OutputUnitInfo.internal();
+ _readBasicInfo(info);
+ info.imports = source.readList(source.readString);
+ return info;
+ });
+}
+
+const int _staticMask = 1 << 3;
+const int _constMask = 1 << 2;
+const int _factoryMask = 1 << 1;
+const int _externalMask = 1 << 0;
diff --git a/lib/src/binary/sink.dart b/lib/src/binary/sink.dart
new file mode 100644
index 0000000..7cacadc
--- /dev/null
+++ b/lib/src/binary/sink.dart
@@ -0,0 +1,401 @@
+// 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:convert';
+import 'dart:typed_data';
+
+/// Interface for serialization.
+// TODO(sigmund): share this with pkg:compiler/src/serialization/*
+abstract class DataSink {
+ /// The amount of data written to this data sink.
+ ///
+ /// The units is based on the underlying data structure for this data sink.
+ int get length;
+
+ /// Flushes any pending data and closes this data sink.
+ ///
+ /// The data sink can no longer be written to after closing.
+ void close();
+
+ /// Writes a reference to [value] to this data sink. If [value] has not yet
+ /// been serialized, [f] is called to serialize the value itself.
+ void writeCached<E>(E value, void f(E value));
+
+ /// Writes the potentially `null` [value] to this data sink. If [value] is
+ /// non-null [f] is called to write the non-null value to the data sink.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSource.readValueOrNull].
+ void writeValueOrNull<E>(E value, void f(E value));
+
+ /// Writes the [values] to this data sink calling [f] to write each value to
+ /// the data sink. If [allowNull] is `true`, [values] is allowed to be `null`.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSource.readList].
+ void writeList<E>(Iterable<E> values, void f(E value),
+ {bool allowNull: false});
+
+ /// Writes the boolean [value] to this data sink.
+ void writeBool(bool value);
+
+ /// Writes the non-negative integer [value] to this data sink.
+ void writeInt(int value);
+
+ /// Writes the potentially `null` non-negative [value] to this data sink.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSource.readIntOrNull].
+ void writeIntOrNull(int value);
+
+ /// Writes the string [value] to this data sink.
+ void writeString(String value);
+
+ /// Writes the potentially `null` string [value] to this data sink.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSource.readStringOrNull].
+ void writeStringOrNull(String value);
+
+ /// Writes the string [values] to this data sink. If [allowNull] is `true`,
+ /// [values] is allowed to be `null`.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSource.readStrings].
+ void writeStrings(Iterable<String> values, {bool allowNull: false});
+
+ /// Writes the [map] from string to [V] values to this data sink, calling [f]
+ /// to write each value to the data sink. If [allowNull] is `true`, [map] is
+ /// allowed to be `null`.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSource.readStringMap].
+ void writeStringMap<V>(Map<String, V> map, void f(V value),
+ {bool allowNull: false});
+
+ /// Writes the enum value [value] to this data sink.
+ // TODO(johnniwinther): Change the signature to
+ // `void writeEnum<E extends Enum<E>>(E value);` when an interface for enums
+ // is added to the language.
+ void writeEnum(dynamic value);
+
+ /// Writes the URI [value] to this data sink.
+ void writeUri(Uri value);
+}
+
+/// Mixin that implements all convenience methods of [DataSink].
+abstract class DataSinkMixin implements DataSink {
+ @override
+ void writeIntOrNull(int value) {
+ writeBool(value != null);
+ if (value != null) {
+ writeInt(value);
+ }
+ }
+
+ @override
+ void writeStringOrNull(String value) {
+ writeBool(value != null);
+ if (value != null) {
+ writeString(value);
+ }
+ }
+
+ @override
+ void writeStrings(Iterable<String> values, {bool allowNull: false}) {
+ if (values == null) {
+ assert(allowNull);
+ writeInt(0);
+ } else {
+ writeInt(values.length);
+ for (String value in values) {
+ writeString(value);
+ }
+ }
+ }
+
+ @override
+ void writeStringMap<V>(Map<String, V> map, void f(V value),
+ {bool allowNull: false}) {
+ if (map == null) {
+ assert(allowNull);
+ writeInt(0);
+ } else {
+ writeInt(map.length);
+ map.forEach((String key, V value) {
+ writeString(key);
+ f(value);
+ });
+ }
+ }
+
+ @override
+ void writeList<E>(Iterable<E> values, void f(E value),
+ {bool allowNull: false}) {
+ if (values == null) {
+ assert(allowNull);
+ writeInt(0);
+ } else {
+ writeInt(values.length);
+ values.forEach(f);
+ }
+ }
+
+ @override
+ void writeValueOrNull<E>(E value, void f(E value)) {
+ writeBool(value != null);
+ if (value != null) {
+ f(value);
+ }
+ }
+}
+
+/// Data sink helper that canonicalizes [E] values using indices.
+class IndexedSink<E> {
+ final void Function(int) _writeInt;
+ final Map<E, int> _cache = {};
+
+ IndexedSink(this._writeInt);
+
+ /// Write a reference to [value] to the data sink.
+ ///
+ /// If [value] has not been canonicalized yet, [writeValue] is called to
+ /// serialize the [value] itself.
+ void write(E value, void writeValue(E value)) {
+ int index = _cache[value];
+ if (index == null) {
+ index = _cache.length;
+ _cache[value] = index;
+ _writeInt(index);
+ writeValue(value);
+ } else {
+ _writeInt(index);
+ }
+ }
+}
+
+/// Base implementation of [DataSink] using [DataSinkMixin] to implement
+/// convenience methods.
+abstract class AbstractDataSink extends DataSinkMixin implements DataSink {
+ IndexedSink<String> _stringIndex;
+ IndexedSink<Uri> _uriIndex;
+ Map<Type, IndexedSink> _generalCaches = {};
+
+ AbstractDataSink() {
+ _stringIndex = new IndexedSink<String>(_writeIntInternal);
+ _uriIndex = new IndexedSink<Uri>(_writeIntInternal);
+ }
+
+ @override
+ void writeCached<E>(E value, void f(E value)) {
+ IndexedSink sink =
+ _generalCaches[E] ??= new IndexedSink<E>(_writeIntInternal);
+ sink.write(value, (v) => f(v));
+ }
+
+ @override
+ void writeEnum(dynamic value) {
+ _writeEnumInternal(value);
+ }
+
+ @override
+ void writeBool(bool value) {
+ assert(value != null);
+ _writeIntInternal(value ? 1 : 0);
+ }
+
+ @override
+ void writeUri(Uri value) {
+ assert(value != null);
+ _writeUri(value);
+ }
+
+ @override
+ void writeString(String value) {
+ assert(value != null);
+ _writeString(value);
+ }
+
+ @override
+ void writeInt(int value) {
+ assert(value != null);
+ assert(value >= 0 && value >> 30 == 0);
+ _writeIntInternal(value);
+ }
+
+ void _writeString(String value) {
+ _stringIndex.write(value, _writeStringInternal);
+ }
+
+ void _writeUri(Uri value) {
+ _uriIndex.write(value, _writeUriInternal);
+ }
+
+ /// Actual serialization of a URI value, implemented by subclasses.
+ void _writeUriInternal(Uri value);
+
+ /// Actual serialization of a String value, implemented by subclasses.
+ void _writeStringInternal(String value);
+
+ /// Actual serialization of a non-negative integer value, implemented by
+ /// subclasses.
+ void _writeIntInternal(int value);
+
+ /// Actual serialization of an enum value, implemented by subclasses.
+ void _writeEnumInternal(dynamic value);
+}
+
+/// [DataSink] that writes data as a sequence of bytes.
+///
+/// This data sink works together with [BinarySource].
+class BinarySink extends AbstractDataSink {
+ final Sink<List<int>> sink;
+ BufferedSink _bufferedSink;
+ int _length = 0;
+
+ BinarySink(this.sink) : _bufferedSink = new BufferedSink(sink);
+
+ @override
+ void _writeUriInternal(Uri value) {
+ _writeString(value.toString());
+ }
+
+ @override
+ void _writeStringInternal(String value) {
+ List<int> bytes = utf8.encode(value);
+ _writeIntInternal(bytes.length);
+ _bufferedSink.addBytes(bytes);
+ _length += bytes.length;
+ }
+
+ @override
+ void _writeIntInternal(int value) {
+ assert(value >= 0 && value >> 30 == 0);
+ if (value < 0x80) {
+ _bufferedSink.addByte(value);
+ _length += 1;
+ } else if (value < 0x4000) {
+ _bufferedSink.addByte2((value >> 8) | 0x80, value & 0xFF);
+ _length += 2;
+ } else {
+ _bufferedSink.addByte4((value >> 24) | 0xC0, (value >> 16) & 0xFF,
+ (value >> 8) & 0xFF, value & 0xFF);
+ _length += 4;
+ }
+ }
+
+ @override
+ void _writeEnumInternal(dynamic value) {
+ _writeIntInternal(value.index);
+ }
+
+ void close() {
+ _bufferedSink.flushAndDestroy();
+ _bufferedSink = null;
+ sink.close();
+ }
+
+ /// Returns the number of bytes written to this data sink.
+ int get length => _length;
+}
+
+/// Puts a buffer in front of a [Sink<List<int>>].
+// TODO(sigmund): share with the implementation in
+// package:kernel/binary/ast_to_binary.dart
+class BufferedSink {
+ static const int SIZE = 100000;
+ static const int SAFE_SIZE = SIZE - 5;
+ static const int SMALL = 10000;
+ final Sink<List<int>> _sink;
+ Uint8List _buffer = new Uint8List(SIZE);
+ int length = 0;
+ int flushedLength = 0;
+
+ Float64List _doubleBuffer = new Float64List(1);
+ Uint8List _doubleBufferUint8;
+
+ int get offset => length + flushedLength;
+
+ BufferedSink(this._sink);
+
+ void addDouble(double d) {
+ _doubleBufferUint8 ??= _doubleBuffer.buffer.asUint8List();
+ _doubleBuffer[0] = d;
+ addByte4(_doubleBufferUint8[0], _doubleBufferUint8[1],
+ _doubleBufferUint8[2], _doubleBufferUint8[3]);
+ addByte4(_doubleBufferUint8[4], _doubleBufferUint8[5],
+ _doubleBufferUint8[6], _doubleBufferUint8[7]);
+ }
+
+ void addByte(int byte) {
+ _buffer[length++] = byte;
+ if (length == SIZE) {
+ _sink.add(_buffer);
+ _buffer = new Uint8List(SIZE);
+ length = 0;
+ flushedLength += SIZE;
+ }
+ }
+
+ void addByte2(int byte1, int byte2) {
+ if (length < SAFE_SIZE) {
+ _buffer[length++] = byte1;
+ _buffer[length++] = byte2;
+ } else {
+ addByte(byte1);
+ addByte(byte2);
+ }
+ }
+
+ void addByte4(int byte1, int byte2, int byte3, int byte4) {
+ if (length < SAFE_SIZE) {
+ _buffer[length++] = byte1;
+ _buffer[length++] = byte2;
+ _buffer[length++] = byte3;
+ _buffer[length++] = byte4;
+ } else {
+ addByte(byte1);
+ addByte(byte2);
+ addByte(byte3);
+ addByte(byte4);
+ }
+ }
+
+ void addBytes(List<int> bytes) {
+ // Avoid copying a large buffer into the another large buffer. Also, if
+ // the bytes buffer is too large to fit in our own buffer, just emit both.
+ if (length + bytes.length < SIZE &&
+ (bytes.length < SMALL || length < SMALL)) {
+ _buffer.setRange(length, length + bytes.length, bytes);
+ length += bytes.length;
+ } else if (bytes.length < SMALL) {
+ // Flush as much as we can in the current buffer.
+ _buffer.setRange(length, SIZE, bytes);
+ _sink.add(_buffer);
+ // Copy over the remainder into a new buffer. It is guaranteed to fit
+ // because the input byte array is small.
+ int alreadyEmitted = SIZE - length;
+ int remainder = bytes.length - alreadyEmitted;
+ _buffer = new Uint8List(SIZE);
+ _buffer.setRange(0, remainder, bytes, alreadyEmitted);
+ length = remainder;
+ flushedLength += SIZE;
+ } else {
+ flush();
+ _sink.add(bytes);
+ flushedLength += bytes.length;
+ }
+ }
+
+ void flush() {
+ _sink.add(_buffer.sublist(0, length));
+ _buffer = new Uint8List(SIZE);
+ flushedLength += length;
+ length = 0;
+ }
+
+ void flushAndDestroy() {
+ _sink.add(_buffer.sublist(0, length));
+ }
+}
diff --git a/lib/src/binary/source.dart b/lib/src/binary/source.dart
new file mode 100644
index 0000000..5aee23b
--- /dev/null
+++ b/lib/src/binary/source.dart
@@ -0,0 +1,299 @@
+// 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:typed_data';
+import 'dart:convert';
+
+/// Interface for deserialization.
+// TODO(sigmund): share this with pkg:compiler/src/serialization/*
+abstract class DataSource {
+ /// Reads a reference to an [E] value from this data source. If the value has
+ /// not yet been deserialized, [f] is called to deserialize the value itself.
+ E readCached<E>(E f());
+
+ /// Reads a potentially `null` [E] value from this data source, calling [f] to
+ /// read the non-null value from the data source.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSink.writeValueOrNull].
+ E readValueOrNull<E>(E f());
+
+ /// Reads a list of [E] values from this data source. If [emptyAsNull] is
+ /// `true`, `null` is returned instead of an empty list.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSink.writeList].
+ List<E> readList<E>(E f(), {bool emptyAsNull: false});
+
+ /// Reads a boolean value from this data source.
+ bool readBool();
+
+ /// Reads a non-negative integer value from this data source.
+ int readInt();
+
+ /// Reads a potentially `null` non-negative integer value from this data
+ /// source.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSink.writeIntOrNull].
+ int readIntOrNull();
+
+ /// Reads a string value from this data source.
+ String readString();
+
+ /// Reads a potentially `null` string value from this data source.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSink.writeStringOrNull].
+ String readStringOrNull();
+
+ /// Reads a list of string values from this data source. If [emptyAsNull] is
+ /// `true`, `null` is returned instead of an empty list.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSink.writeStrings].
+ List<String> readStrings({bool emptyAsNull: false});
+
+ /// Reads a map from string values to [V] values from this data source,
+ /// calling [f] to read each value from the data source. If [emptyAsNull] is
+ /// `true`, `null` is returned instead of an empty map.
+ ///
+ /// This is a convenience method to be used together with
+ /// [DataSink.writeStringMap].
+ Map<String, V> readStringMap<V>(V f(), {bool emptyAsNull: false});
+
+ /// Reads an enum value from the list of enum [values] from this data source.
+ ///
+ /// The [values] argument is intended to be the static `.values` field on
+ /// enum classes, for instance:
+ ///
+ /// enum Foo { bar, baz }
+ /// ...
+ /// Foo foo = source.readEnum(Foo.values);
+ ///
+ E readEnum<E>(List<E> values);
+
+ /// Reads a URI value from this data source.
+ Uri readUri();
+}
+
+/// Mixin that implements all convenience methods of [DataSource].
+abstract class DataSourceMixin implements DataSource {
+ @override
+ E readValueOrNull<E>(E f()) {
+ bool hasValue = readBool();
+ if (hasValue) {
+ return f();
+ }
+ return null;
+ }
+
+ @override
+ List<E> readList<E>(E f(), {bool emptyAsNull: false}) {
+ int count = readInt();
+ if (count == 0 && emptyAsNull) return null;
+ List<E> list = new List<E>(count);
+ for (int i = 0; i < count; i++) {
+ list[i] = f();
+ }
+ return list;
+ }
+
+ @override
+ int readIntOrNull() {
+ bool hasValue = readBool();
+ if (hasValue) {
+ return readInt();
+ }
+ return null;
+ }
+
+ @override
+ String readStringOrNull() {
+ bool hasValue = readBool();
+ if (hasValue) {
+ return readString();
+ }
+ return null;
+ }
+
+ @override
+ List<String> readStrings({bool emptyAsNull: false}) {
+ int count = readInt();
+ if (count == 0 && emptyAsNull) return null;
+ List<String> list = new List<String>(count);
+ for (int i = 0; i < count; i++) {
+ list[i] = readString();
+ }
+ return list;
+ }
+
+ @override
+ Map<String, V> readStringMap<V>(V f(), {bool emptyAsNull: false}) {
+ int count = readInt();
+ if (count == 0 && emptyAsNull) return null;
+ Map<String, V> map = {};
+ for (int i = 0; i < count; i++) {
+ String key = readString();
+ V value = f();
+ map[key] = value;
+ }
+ return map;
+ }
+}
+
+/// Data source helper reads canonicalized [E] values through indices.
+class IndexedSource<E> {
+ final int Function() _readInt;
+ final List<E> _cache = [];
+ final Set<int> _pending = new Set();
+
+ IndexedSource(this._readInt);
+
+ /// Reads a reference to an [E] value from the data source.
+ ///
+ /// If the value hasn't yet been read, [readValue] is called to deserialize
+ /// the value itself.
+ E read(E readValue()) {
+ int index = _readInt();
+ if (_pending.contains(index)) throw "serialization cycles not supported";
+ if (index >= _cache.length) {
+ _pending.add(index);
+ _cache.add(null);
+ E value = readValue();
+ _pending.remove(index);
+ _cache[index] = value;
+ return value;
+ } else {
+ return _cache[index];
+ }
+ }
+}
+
+/// Base implementation of [DataSource] using [DataSourceMixin] to implement
+/// convenience methods.
+abstract class AbstractDataSource extends DataSourceMixin
+ implements DataSource {
+ IndexedSource<String> _stringIndex;
+ IndexedSource<Uri> _uriIndex;
+ Map<Type, IndexedSource> _generalCaches = {};
+
+ AbstractDataSource() {
+ _stringIndex = new IndexedSource<String>(_readIntInternal);
+ _uriIndex = new IndexedSource<Uri>(_readIntInternal);
+ }
+
+ @override
+ E readCached<E>(E f()) {
+ IndexedSource source =
+ _generalCaches[E] ??= new IndexedSource<E>(_readIntInternal);
+ return source.read(f);
+ }
+
+ @override
+ E readEnum<E>(List<E> values) {
+ return _readEnumInternal(values);
+ }
+
+ @override
+ Uri readUri() {
+ return _readUri();
+ }
+
+ Uri _readUri() {
+ return _uriIndex.read(_readUriInternal);
+ }
+
+ @override
+ bool readBool() {
+ int value = _readIntInternal();
+ assert(value == 0 || value == 1);
+ return value == 1;
+ }
+
+ @override
+ String readString() {
+ return _readString();
+ }
+
+ String _readString() {
+ return _stringIndex.read(_readStringInternal);
+ }
+
+ @override
+ int readInt() {
+ return _readIntInternal();
+ }
+
+ /// Actual deserialization of a string value, implemented by subclasses.
+ String _readStringInternal();
+
+ /// Actual deserialization of a non-negative integer value, implemented by
+ /// subclasses.
+ int _readIntInternal();
+
+ /// Actual deserialization of a URI value, implemented by subclasses.
+ Uri _readUriInternal();
+
+ /// Actual deserialization of an enum value in [values], implemented by
+ /// subclasses.
+ E _readEnumInternal<E>(List<E> values);
+}
+
+/// [DataSource] that reads data from a sequence of bytes.
+///
+/// This data source works together with [BinarySink].
+class BinarySource extends AbstractDataSource {
+ int _byteOffset = 0;
+ final List<int> _bytes;
+
+ BinarySource(this._bytes);
+ int _readByte() => _bytes[_byteOffset++];
+
+ @override
+ String _readStringInternal() {
+ int length = _readIntInternal();
+ List<int> bytes = new Uint8List(length);
+ bytes.setRange(0, bytes.length, _bytes, _byteOffset);
+ _byteOffset += bytes.length;
+ return utf8.decode(bytes);
+ }
+
+ @override
+ int _readIntInternal() {
+ 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();
+ }
+ }
+
+ @override
+ Uri _readUriInternal() {
+ String text = _readString();
+ return Uri.parse(text);
+ }
+
+ @override
+ E _readEnumInternal<E>(List<E> values) {
+ int index = _readIntInternal();
+ assert(
+ 0 <= index && index < values.length,
+ "Invalid data kind index. "
+ "Expected one of $values, found index $index.");
+ return values[index];
+ }
+
+ @override
+ String get _errorContext => ' Offset $_byteOffset in ${_bytes.length}.';
+}
diff --git a/test/binary_codec_test.dart b/test/binary_codec_test.dart
new file mode 100644
index 0000000..edbacb9
--- /dev/null
+++ b/test/binary_codec_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2015, 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:convert';
+import 'dart:io';
+
+import 'package:dart2js_info/json_info_codec.dart';
+import 'package:dart2js_info/binary_codec.dart' as binary;
+import 'package:test/test.dart';
+
+class ByteSink implements Sink<List<int>> {
+ BytesBuilder builder = new BytesBuilder();
+
+ add(List<int> data) => builder.add(data);
+ close() {}
+}
+
+main() {
+ group('json to proto conversion with deferred files', () {
+ test('hello_world_deferred', () {
+ var helloWorld = new File(
+ 'test/hello_world_deferred/hello_world_deferred.js.info.json');
+ var contents = helloWorld.readAsStringSync();
+ var json = jsonDecode(contents);
+ var info = new AllInfoJsonCodec().decode(json);
+
+ var sink = new ByteSink();
+ binary.encode(info, sink);
+ var info2 = binary.decode(sink.builder.toBytes());
+ var json2 = new AllInfoJsonCodec().encode(info2);
+
+ info.program.toJsonDuration = new Duration(milliseconds: 0);
+ var json1 = new AllInfoJsonCodec().encode(info);
+ var contents1 = const JsonEncoder.withIndent(" ").convert(json1);
+ var contents2 = const JsonEncoder.withIndent(" ").convert(json2);
+ expect(contents1 == contents2, isTrue);
+ });
+ });
+}