blob: 687b4f2521f340d3097d30430047707c20df1c08 [file] [log] [blame]
// Copyright (c) 2020, 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/summary2/ast_binary_tag.dart';
import 'package:analyzer/src/summary2/ast_binary_writer.dart';
import 'package:analyzer/src/summary2/binary_format_doc.dart';
import 'package:analyzer/src/summary2/data_writer.dart';
import 'package:analyzer/src/summary2/reference.dart';
Uint8List writeUnitToBytes({required CompilationUnit unit}) {
var byteSink = ByteSink();
var sink = BufferedSink(byteSink);
var stringIndexer = StringIndexer();
var headerOffset = sink.offset;
var nextResolutionIndex = 0;
var unitWriter = AstBinaryWriter(
withInformative: true,
sink: sink,
stringIndexer: stringIndexer,
getNextResolutionIndex: () => nextResolutionIndex++,
resolutionSink: null,
);
unit.accept(unitWriter);
void _writeStringReference(String string) {
var index = stringIndexer[string];
sink.writeUInt30(index);
}
var indexOffset = sink.offset;
sink.writeUInt30(unitWriter.unitMemberIndexItems.length);
for (var declaration in unitWriter.unitMemberIndexItems) {
sink.writeUInt30(declaration.offset);
sink.writeByte(declaration.tag);
declaration.name.map((name) {
_writeStringReference(name);
}, (variableNames) {
sink.writeList(variableNames, _writeStringReference);
});
if (declaration.classIndexOffset != 0) {
sink.writeUInt30(declaration.classIndexOffset);
}
}
var libraryDataOffset = sink.offset;
{
var name = '';
var nameOffset = -1;
var nameLength = 0;
for (var directive in unit.directives) {
if (directive is LibraryDirective) {
name = directive.name.components.map((e) => e.name).join('.');
nameOffset = directive.name.offset;
nameLength = directive.name.length;
break;
}
}
var hasPartOfDirective = false;
for (var directive in unit.directives) {
if (directive is PartOfDirective) {
hasPartOfDirective = true;
break;
}
}
_writeStringReference(name);
sink.writeUInt30(nameOffset + 1);
sink.writeUInt30(nameLength);
sink.writeByte(hasPartOfDirective ? 1 : 0);
sink.writeByte(1); // withInformative
}
var stringTableOffset = stringIndexer.write(sink);
sink.writeUInt32(headerOffset);
sink.writeUInt32(indexOffset);
sink.writeUInt32(libraryDataOffset);
sink.writeUInt32(stringTableOffset);
sink.flushAndDestroy();
return byteSink.builder.takeBytes();
}
class BundleWriter {
final bool withInformative;
late final BundleWriterAst _astWriter;
late final BundleWriterResolution _resolutionWriter;
BundleWriter(this.withInformative, Reference dynamicReference) {
_astWriter = BundleWriterAst(withInformative);
_resolutionWriter = BundleWriterResolution(dynamicReference);
}
void addLibraryAst(LibraryToWriteAst library) {
var astUnitOffsets = <int>[];
for (var unit in library.units) {
var offset = _astWriter.writeUnit(unit.node);
astUnitOffsets.add(offset);
}
_astWriter.writeLibrary(library.units[0].node, astUnitOffsets);
}
void addLibraryResolution(LibraryToWriteResolution library) {
var resolutionLibrary = _resolutionWriter.enterLibrary(library);
for (var unit in library.units) {
var resolutionUnit = resolutionLibrary.enterUnit(unit);
// TODO(scheglov) Is it better to have a throwaway Object, or null?
var notUsedSink = BufferedSink(ByteSink());
var notUsedStringIndexer = StringIndexer();
var unitWriter = AstBinaryWriter(
withInformative: withInformative,
sink: notUsedSink,
stringIndexer: notUsedStringIndexer,
getNextResolutionIndex: resolutionUnit.enterDeclaration,
resolutionSink: resolutionUnit.library.sink,
);
unit.node.accept(unitWriter);
}
}
BundleWriterResult finish() {
var astBytes = _astWriter.finish();
var resolutionBytes = _resolutionWriter.finish();
return BundleWriterResult(
astBytes: astBytes,
resolutionBytes: resolutionBytes,
);
}
}
class BundleWriterAst {
final bool withInformative;
final ByteSink _byteSink = ByteSink();
late final BufferedSink sink;
final StringIndexer stringIndexer = StringIndexer();
final List<int> _libraryOffsets = [];
BundleWriterAst(this.withInformative) {
sink = BufferedSink(_byteSink);
sink.writeByte(withInformative ? 1 : 0);
}
Uint8List finish() {
var librariesOffset = sink.offset;
sink.writeUint30List(_libraryOffsets);
var stringTableOffset = stringIndexer.write(sink);
sink.writeUInt32(librariesOffset);
sink.writeUInt32(stringTableOffset);
sink.flushAndDestroy();
return _byteSink.builder.takeBytes();
}
/// Write the library name and offset, and pointers to [unitOffsets].
void writeLibrary(CompilationUnit definingUnit, List<int> unitOffsets) {
_libraryOffsets.add(sink.offset);
var name = '';
var nameOffset = -1;
var nameLength = 0;
for (var directive in definingUnit.directives) {
if (directive is LibraryDirective) {
name = directive.name.components.map((e) => e.name).join('.');
nameOffset = directive.name.offset;
nameLength = directive.name.length;
break;
}
}
var hasPartOfDirective = false;
for (var directive in definingUnit.directives) {
if (directive is PartOfDirective) {
hasPartOfDirective = true;
break;
}
}
_writeStringReference(name);
sink.writeUInt30(1 + nameOffset);
sink.writeUInt30(nameLength);
sink.writeByte(hasPartOfDirective ? 1 : 0);
sink.writeUint30List(unitOffsets);
}
/// Write the [node] into the [sink].
///
/// Return the pointer at [AstUnitFormat.headerOffset].
///
/// TODO(scheglov) looks very similar to [writeUnitToBytes]
int writeUnit(CompilationUnit node) {
var headerOffset = sink.offset;
var nextResolutionIndex = 0;
var unitWriter = AstBinaryWriter(
withInformative: withInformative,
sink: sink,
stringIndexer: stringIndexer,
getNextResolutionIndex: () => nextResolutionIndex++,
resolutionSink: null,
);
node.accept(unitWriter);
var indexOffset = sink.offset;
sink.writeUInt30(headerOffset);
sink.writeUInt30(unitWriter.unitMemberIndexItems.length);
for (var declaration in unitWriter.unitMemberIndexItems) {
sink.writeUInt30(declaration.offset);
sink.writeByte(declaration.tag);
declaration.name.map((name) {
_writeStringReference(name);
}, (variableNames) {
sink.writeList(variableNames, _writeStringReference);
});
if (declaration.classIndexOffset != 0) {
sink.writeUInt30(declaration.classIndexOffset);
}
}
return indexOffset;
}
void _writeStringReference(String string) {
var index = stringIndexer[string];
sink.writeUInt30(index);
}
}
class BundleWriterResolution {
late final _BundleWriterReferences _references;
final ByteSink _byteSink = ByteSink();
late final BufferedSink _sink;
late final ResolutionSink _resolutionSink;
final StringIndexer _stringIndexer = StringIndexer();
final List<_ResolutionLibrary> _libraries = [];
BundleWriterResolution(Reference dynamicReference) {
_references = _BundleWriterReferences(dynamicReference);
_sink = BufferedSink(_byteSink);
_resolutionSink = ResolutionSink(
stringIndexer: _stringIndexer,
sink: _sink,
references: _references,
);
}
_ResolutionLibrary enterLibrary(LibraryToWriteResolution libraryToWrite) {
var library = _ResolutionLibrary(
sink: _resolutionSink,
library: libraryToWrite,
);
_libraries.add(library);
return library;
}
Uint8List finish() {
var libraryOffsets = <int>[];
for (var library in _libraries) {
var unitOffsets = <int>[];
for (var unit in library.units) {
unitOffsets.add(_sink.offset);
_writeStringReference(unit.unit.uriStr);
_sink.writeByte(unit.unit.isSynthetic ? 1 : 0);
_sink.writeByte(unit.unit.partUriStr != null ? 1 : 0);
_writeStringReference(unit.unit.partUriStr ?? '');
_sink.writeUInt30(unit.directivesOffset);
_sink.writeUint30List(unit.offsets);
}
libraryOffsets.add(_sink.offset);
_writeStringReference(library.library.uriStr);
_sink.writeUint30List(unitOffsets);
_writeReferences(library.library.exports);
}
_references._clearIndexes();
var librariesOffset = _sink.offset;
_sink.writeUint30List(libraryOffsets);
var referencesOffset = _sink.offset;
_sink.writeUint30List(_references._referenceParents);
_writeStringList(_references._referenceNames);
var stringTableOffset = _stringIndexer.write(_sink);
// Write as Uint32 so that we know where it is.
_sink.writeUInt32(librariesOffset);
_sink.writeUInt32(referencesOffset);
_sink.writeUInt32(stringTableOffset);
_sink.flushAndDestroy();
return _byteSink.builder.takeBytes();
}
void _writeReferences(List<Reference> references) {
var length = references.length;
_sink.writeUInt30(length);
for (var reference in references) {
var index = _references._indexOfReference(reference);
_sink.writeUInt30(index);
}
}
void _writeStringList(List<String> values) {
_sink.writeUInt30(values.length);
for (var value in values) {
_writeStringReference(value);
}
}
void _writeStringReference(String string) {
var index = _stringIndexer[string];
_sink.writeUInt30(index);
}
}
class BundleWriterResult {
final Uint8List astBytes;
final Uint8List resolutionBytes;
BundleWriterResult({
required this.astBytes,
required this.resolutionBytes,
});
}
class LibraryToWriteAst {
final List<UnitToWriteAst> units;
LibraryToWriteAst({
required this.units,
});
}
class LibraryToWriteResolution {
final String uriStr;
final List<Reference> exports;
final List<UnitToWriteResolution> units;
LibraryToWriteResolution({
required this.uriStr,
required this.exports,
required this.units,
});
}
class ResolutionSink {
final StringIndexer _stringIndexer;
final BufferedSink _sink;
final _BundleWriterReferences _references2;
final _LocalElementIndexer localElements = _LocalElementIndexer();
ResolutionSink({
required StringIndexer stringIndexer,
required BufferedSink sink,
required _BundleWriterReferences references,
}) : _stringIndexer = stringIndexer,
_sink = sink,
_references2 = references;
int get offset => _sink.offset;
void writeByte(int byte) {
assert((byte & 0xFF) == byte);
_sink.addByte(byte);
}
/// TODO(scheglov) Triage places where we write elements.
/// Some of then cannot be members, e.g. type names.
void writeElement(Element? element) {
if (element is Member) {
var declaration = element.declaration;
var isLegacy = element.isLegacy;
var typeArguments = _enclosingClassTypeArguments(
declaration,
element.substitution.map,
);
writeByte(
isLegacy
? Tag.MemberLegacyWithTypeArguments
: Tag.MemberWithTypeArguments,
);
writeElement0(declaration);
_writeTypeList(typeArguments);
} else {
writeByte(Tag.RawElement);
writeElement0(element);
}
}
void writeElement0(Element? element) {
assert(element is! Member, 'Use writeMemberOrElement()');
var elementIndex = _indexOfElement(element);
_sink.writeUInt30(elementIndex);
}
void writeStringList(List<String> values) {
_sink.writeUInt30(values.length);
for (var value in values) {
_writeStringReference(value);
}
}
void writeType(DartType? type) {
if (type == null) {
writeByte(Tag.NullType);
} else if (type is DynamicType) {
writeByte(Tag.DynamicType);
} else if (type is FunctionType) {
_writeFunctionType(type);
} else if (type is InterfaceType) {
var typeArguments = type.typeArguments;
var nullabilitySuffix = type.nullabilitySuffix;
if (typeArguments.isEmpty) {
if (nullabilitySuffix == NullabilitySuffix.none) {
writeByte(Tag.InterfaceType_noTypeArguments_none);
} else if (nullabilitySuffix == NullabilitySuffix.question) {
writeByte(Tag.InterfaceType_noTypeArguments_question);
} else if (nullabilitySuffix == NullabilitySuffix.star) {
writeByte(Tag.InterfaceType_noTypeArguments_star);
}
// TODO(scheglov) Write raw
writeElement(type.element);
} else {
writeByte(Tag.InterfaceType);
// TODO(scheglov) Write raw
writeElement(type.element);
_sink.writeUInt30(typeArguments.length);
for (var i = 0; i < typeArguments.length; ++i) {
writeType(typeArguments[i]);
}
_writeNullabilitySuffix(nullabilitySuffix);
}
} else if (type is NeverType) {
writeByte(Tag.NeverType);
_writeNullabilitySuffix(type.nullabilitySuffix);
} else if (type is TypeParameterType) {
writeByte(Tag.TypeParameterType);
writeElement(type.element);
_writeNullabilitySuffix(type.nullabilitySuffix);
} else if (type is VoidType) {
writeByte(Tag.VoidType);
} else {
// TODO
throw UnimplementedError('${type.runtimeType}');
}
}
void writeUInt30(int value) {
_sink.writeUInt30(value);
}
int _indexOfElement(Element? element) {
if (element == null) return 0;
if (element is MultiplyDefinedElement) return 0;
assert(element is! Member);
// Positional parameters cannot be referenced outside of their scope,
// so don't have a reference, so are stored as local elements.
if (element is ParameterElementImpl && element.reference == null) {
return localElements[element] << 1 | 0x1;
}
// Type parameters cannot be referenced outside of their scope,
// so don't have a reference, so are stored as local elements.
if (element is TypeParameterElement) {
return localElements[element] << 1 | 0x1;
}
if (identical(element, DynamicElementImpl.instance)) {
return _references2._indexOfReference(_references2.dynamicReference) << 1;
}
var reference = (element as ElementImpl).reference;
return _references2._indexOfReference(reference) << 1;
}
void _writeFormalParameterKind(ParameterElement p) {
if (p.isRequiredPositional) {
writeByte(Tag.ParameterKindRequiredPositional);
} else if (p.isOptionalPositional) {
writeByte(Tag.ParameterKindOptionalPositional);
} else if (p.isRequiredNamed) {
writeByte(Tag.ParameterKindRequiredNamed);
} else if (p.isOptionalNamed) {
writeByte(Tag.ParameterKindOptionalNamed);
} else {
throw StateError('Unexpected parameter kind: $p');
}
}
void _writeFunctionType(FunctionType type) {
type = _toSyntheticFunctionType(type);
writeByte(Tag.FunctionType);
localElements.pushScope();
var typeParameters = type.typeFormals;
for (var typeParameter in type.typeFormals) {
localElements.declare(typeParameter);
}
_sink.writeUInt30(typeParameters.length);
for (var typeParameter in type.typeFormals) {
_writeStringReference(typeParameter.name);
}
for (var typeParameter in type.typeFormals) {
writeType(typeParameter.bound);
}
var aliasElement = type.aliasElement;
writeElement(aliasElement);
if (aliasElement != null) {
_writeTypeList(type.aliasArguments!);
}
writeType(type.returnType);
var parameters = type.parameters;
_sink.writeUInt30(parameters.length);
for (var parameter in parameters) {
_writeFormalParameterKind(parameter);
writeType(parameter.type);
// TODO(scheglov) Don't write names of positional parameters
_writeStringReference(parameter.name);
}
_writeNullabilitySuffix(type.nullabilitySuffix);
localElements.popScope();
}
void _writeNullabilitySuffix(NullabilitySuffix suffix) {
writeByte(suffix.index);
}
void _writeStringReference(String string) {
var index = _stringIndexer[string];
_sink.writeUInt30(index);
}
void _writeTypeList(List<DartType> types) {
_sink.writeUInt30(types.length);
for (var type in types) {
writeType(type);
}
}
static List<DartType> _enclosingClassTypeArguments(
Element declaration,
Map<TypeParameterElement, DartType> substitution,
) {
// TODO(scheglov) Just keep it null in class Member?
if (substitution.isEmpty) {
return const [];
}
var enclosing = declaration.enclosingElement;
if (enclosing is TypeParameterizedElement) {
if (enclosing is! ClassElement && enclosing is! ExtensionElement) {
return const <DartType>[];
}
var typeParameters = enclosing.typeParameters;
if (typeParameters.isEmpty) {
return const <DartType>[];
}
return typeParameters
.map((typeParameter) => substitution[typeParameter]!)
.toList(growable: false);
}
return const <DartType>[];
}
static FunctionType _toSyntheticFunctionType(FunctionType type) {
var typeParameters = type.typeFormals;
if (typeParameters.isEmpty) return type;
var onlySyntheticTypeParameters = typeParameters.every((e) {
return e is TypeParameterElementImpl && e.linkedNode == null;
});
if (onlySyntheticTypeParameters) return type;
var parameters = getFreshTypeParameters(typeParameters);
return parameters.applyToFunctionType(type);
}
}
class ResolutionUnit {
final _ResolutionLibrary library;
final UnitToWriteResolution unit;
/// The offset of the resolution data for directives.
final int directivesOffset;
/// The offsets of resolution data for each declaration - class, method, etc.
final List<int> offsets = [];
ResolutionUnit({
required this.library,
required this.unit,
required this.directivesOffset,
});
/// Should be called on enter into a new declaration on which level
/// resolution is stored, e.g. [ClassDeclaration] (header), or
/// [MethodDeclaration] (header), or [FieldDeclaration] (all).
int enterDeclaration() {
var index = offsets.length;
offsets.add(library.sink.offset);
return index;
}
}
class StringIndexer {
final Map<String, int> _index = {};
int operator [](String string) {
var result = _index[string];
if (result == null) {
result = _index.length;
_index[string] = result;
}
return result;
}
int write(BufferedSink sink) {
var bytesOffset = sink.offset;
var length = _index.length;
var lengths = Uint32List(length);
var lengthsIndex = 0;
for (var key in _index.keys) {
var stringStart = sink.offset;
_writeWtf8(sink, key);
lengths[lengthsIndex++] = sink.offset - stringStart;
}
var resultOffset = sink.offset;
var lengthOfBytes = sink.offset - bytesOffset;
sink.writeUInt30(lengthOfBytes);
sink.writeUint30List(lengths);
return resultOffset;
}
/// Write [source] string into [sink].
static void _writeWtf8(BufferedSink sink, String source) {
var end = source.length;
if (end == 0) {
return;
}
int i = 0;
do {
var codeUnit = source.codeUnitAt(i++);
if (codeUnit < 128) {
// ASCII.
sink.addByte(codeUnit);
} else if (codeUnit < 0x800) {
// Two-byte sequence (11-bit unicode value).
sink.addByte(0xC0 | (codeUnit >> 6));
sink.addByte(0x80 | (codeUnit & 0x3f));
} else if ((codeUnit & 0xFC00) == 0xD800 &&
i < end &&
(source.codeUnitAt(i) & 0xFC00) == 0xDC00) {
// Surrogate pair -> four-byte sequence (non-BMP unicode value).
int codeUnit2 = source.codeUnitAt(i++);
int unicode =
0x10000 + ((codeUnit & 0x3FF) << 10) + (codeUnit2 & 0x3FF);
sink.addByte(0xF0 | (unicode >> 18));
sink.addByte(0x80 | ((unicode >> 12) & 0x3F));
sink.addByte(0x80 | ((unicode >> 6) & 0x3F));
sink.addByte(0x80 | (unicode & 0x3F));
} else {
// Three-byte sequence (16-bit unicode value), including lone
// surrogates.
sink.addByte(0xE0 | (codeUnit >> 12));
sink.addByte(0x80 | ((codeUnit >> 6) & 0x3f));
sink.addByte(0x80 | (codeUnit & 0x3f));
}
} while (i < end);
}
}
class UnitToWriteAst {
final CompilationUnit node;
UnitToWriteAst({
required this.node,
});
}
class UnitToWriteResolution {
final String uriStr;
final String? partUriStr;
final CompilationUnit node;
final bool isSynthetic;
UnitToWriteResolution({
required this.uriStr,
required this.partUriStr,
required this.node,
required this.isSynthetic,
});
}
class _BundleWriterReferences {
/// The `dynamic` class is declared in `dart:core`, but is not a class.
/// Also, it is static, so we cannot set `reference` for it.
/// So, we have to push it in a separate way.
final Reference dynamicReference;
/// References used in all libraries being linked.
/// Element references in nodes are indexes in this list.
/// TODO(scheglov) Do we really use this list?
final List<Reference?> references = [null];
final List<int> _referenceParents = [0];
final List<String> _referenceNames = [''];
_BundleWriterReferences(this.dynamicReference);
/// We need indexes for references during linking, but once we are done,
/// we must clear indexes to make references ready for linking a next bundle.
void _clearIndexes() {
for (var reference in references) {
if (reference != null) {
reference.index = null;
}
}
}
int _indexOfReference(Reference? reference) {
if (reference == null) return 0;
if (reference.parent == null) return 0;
var index = reference.index;
if (index != null) return index;
var parentIndex = _indexOfReference(reference.parent);
_referenceParents.add(parentIndex);
_referenceNames.add(reference.name);
index = references.length;
reference.index = index;
references.add(reference);
return index;
}
}
class _LocalElementIndexer {
final Map<Element, int> _index = Map.identity();
final List<int> _scopes = [];
int _stackHeight = 0;
int operator [](Element element) {
return _index[element] ??
(throw ArgumentError('Unexpectedly not indexed: $element'));
}
void declare(Element element) {
_index[element] = _stackHeight++;
}
void popScope() {
_stackHeight = _scopes.removeLast();
}
void pushScope() {
_scopes.add(_stackHeight);
}
}
class _ResolutionLibrary {
final ResolutionSink sink;
final LibraryToWriteResolution library;
final List<ResolutionUnit> units = [];
_ResolutionLibrary({
required this.sink,
required this.library,
});
ResolutionUnit enterUnit(UnitToWriteResolution unitToWrite) {
var unit = ResolutionUnit(
library: this,
unit: unitToWrite,
directivesOffset: sink.offset,
);
units.add(unit);
return unit;
}
}