blob: aa14440769cba95b50025f5ee2218a7984c2b9db [file]
// Copyright (c) 2018, 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 'package:kernel/binary/ast_to_binary.dart';
import 'serialization.dart' show DataSink;
/// [DataSink] that writes data as a sequence of bytes.
///
/// This data sink works together with [BinarySourceWriter].
class BinaryDataSink implements DataSink {
final Sink<List<int>> sink;
// Nullable and non-final to allow storage to be released.
BufferedSink? _bufferedSink;
final Map<int, int> _deferredOffsetToSize = {};
int _length = 0;
BinaryDataSink(this.sink) : _bufferedSink = BufferedSink(sink);
@override
int get length => _length;
@override
void beginTag(String tag) {
// TODO(johnniwinther): Support tags in binary serialization?
}
@override
void endTag(String tag) {
// TODO(johnniwinther): Support tags in binary serialization?
}
@override
void writeString(String value) {
final bytes = _encodeWtf8(value);
writeInt(bytes.length);
_bufferedSink!.addBytes(bytes);
_length += bytes.length;
}
/// We use WTF-8 encoding for strings in serialized data to ensure we preserve
/// Dart strings containing unmatched surrogate pairs and byte order marks.
/// The dart:convert UTF-8 encoder does not preserve these through a round
/// trip.
Uint8List _encodeWtf8(String source) {
int end = source.length;
if (end == 0) return Uint8List(0);
final target = Uint8List(source.length * 4);
int i = 0;
int index = 0;
do {
int codeUnit = source.codeUnitAt(i++);
while (codeUnit < 128) {
// ASCII.
target[index++] = codeUnit;
if (i >= end) return Uint8List.sublistView(target, 0, index);
codeUnit = source.codeUnitAt(i++);
}
if (codeUnit < 0x800) {
// Two-byte sequence (11-bit unicode value).
index += 2;
target[index - 2] = 0xC0 | (codeUnit >> 6);
target[index - 1] = 0x80 | (codeUnit & 0x3F);
} else if ((codeUnit & 0xFC00) == 0xD800 &&
i < end &&
(source.codeUnitAt(i) & 0xFC00) == 0xDC00) {
// Surrogate pair -> four-byte sequence (non-BMP unicode value).
index += 4;
int codeUnit2 = source.codeUnitAt(i++);
int unicode =
0x10000 + ((codeUnit & 0x3FF) << 10) + (codeUnit2 & 0x3FF);
target[index - 4] = 0xF0 | (unicode >> 18);
target[index - 3] = 0x80 | ((unicode >> 12) & 0x3F);
target[index - 2] = 0x80 | ((unicode >> 6) & 0x3F);
target[index - 1] = 0x80 | (unicode & 0x3F);
} else {
// Three-byte sequence (16-bit unicode value), including lone
// surrogates.
index += 3;
target[index - 3] = 0xE0 | (codeUnit >> 12);
target[index - 2] = 0x80 | ((codeUnit >> 6) & 0x3F);
target[index - 1] = 0x80 | (codeUnit & 0x3F);
}
} while (i < end);
return Uint8List.sublistView(target, 0, index);
}
/// In order to compactly represent ints we only support up to 30 bit values.
static const int maxIntValue = 1 << 30;
@override
void writeInt(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 writeUint32(int value) {
_length += 4;
_bufferedSink!.addByte4(
(value >> 24) & 0xFF,
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF,
);
}
@override
void writeDeferred(void Function() writer) {
final indexOffset = _length;
writeInt(0); // Padding so the offset won't collide with a nested write.
final dataStartOffset = _length;
writer();
_deferredOffsetToSize[indexOffset] = _length - dataStartOffset;
}
final List<(int, int)> _deferredOffsets = [];
@override
void startDeferred() {
final indexOffset = _length;
writeInt(0); // Padding so the offset won't collide with a nested write.
final dataStartOffset = _length;
_deferredOffsets.add((indexOffset, dataStartOffset));
}
@override
void endDeferred() {
final (indexOffset, dataStartOffset) = _deferredOffsets.removeLast();
_deferredOffsetToSize[indexOffset] = _length - dataStartOffset;
}
@override
void writeEnum<E extends Enum>(E value) {
writeInt(value.index);
}
@override
void close() {
final deferredDataStart = _length;
writeInt(_deferredOffsetToSize.length);
for (final entry in _deferredOffsetToSize.entries) {
writeInt(entry.key);
writeInt(entry.value);
}
writeUint32(deferredDataStart);
_bufferedSink!.flushAndDestroy();
_bufferedSink = null;
_deferredOffsetToSize.clear();
sink.close();
}
}