| // Copyright (c) 2017, 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' show File; |
| import 'dart:typed_data' show BytesBuilder, Uint8List; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/binary/ast_from_binary.dart'; |
| import 'package:kernel/binary/ast_to_binary.dart'; |
| import 'package:kernel/text/ast_to_text.dart'; |
| import 'package:test/test.dart'; |
| import 'package:front_end/src/compute_platform_binaries_location.dart' |
| show computePlatformBinariesLocation; |
| |
| /// Test metadata: to each node we attach a metadata that contains |
| /// * node formatted as string |
| /// * reference to its enclosing member |
| /// * type representing the first type parameter of its enclosing function |
| class Metadata { |
| final String string; |
| final Reference? _memberRef; |
| final DartType type; |
| |
| Member? get member => _memberRef?.asMember; |
| |
| Metadata.forNode(TreeNode n) |
| : this( |
| n.leakingDebugToString(), |
| // Refers to the member, not about the function => use getter. |
| getMemberReferenceGetter(getMemberForMetadata(n)), |
| getTypeForMetadata(n), |
| ); |
| |
| Metadata(this.string, this._memberRef, this.type); |
| } |
| |
| Member? getMemberForMetadata(TreeNode node) { |
| final parent = node.parent; |
| if (parent == null) return null; |
| if (parent is Member) return parent; |
| return getMemberForMetadata(parent); |
| } |
| |
| DartType getTypeForMetadata(TreeNode node) { |
| final parent = node.parent; |
| if (parent == null) return const VoidType(); |
| if (parent is FunctionNode) { |
| if (parent.typeParameters.isEmpty) { |
| return const VoidType(); |
| } |
| return new TypeParameterType( |
| parent.typeParameters[0], |
| Nullability.nonNullable, |
| ); |
| } |
| return getTypeForMetadata(parent); |
| } |
| |
| class TestMetadataRepository extends MetadataRepository<Metadata> { |
| static const kTag = 'kernel.metadata.test'; |
| |
| @override |
| final String tag = kTag; |
| |
| @override |
| final Map<TreeNode, Metadata> mapping = <TreeNode, Metadata>{}; |
| |
| @override |
| void writeToBinary(Metadata metadata, Node node, BinarySink sink) { |
| expect(metadata, equals(mapping[node])); |
| sink.writeByteList(utf8.encode(metadata.string)); |
| sink.writeStringReference(metadata.string); |
| sink.writeNullAllowedCanonicalNameReference(metadata.member?.reference); |
| sink.writeDartType(metadata.type); |
| } |
| |
| @override |
| Metadata readFromBinary(Node node, BinarySource source) { |
| final string1 = utf8.decode(source.readByteList()); |
| final string2 = source.readStringReference(); |
| final memberRef = source.readNullableCanonicalNameReference()?.reference; |
| final type = source.readDartType(); |
| expect(string1, equals(string2)); |
| return new Metadata(string2, memberRef, type); |
| } |
| } |
| |
| class BytesBuilderSink implements Sink<List<int>> { |
| final builder = new BytesBuilder(copy: false); |
| |
| @override |
| void add(List<int> bytes) { |
| builder.add(bytes); |
| } |
| |
| @override |
| void close() {} |
| } |
| |
| typedef NodePredicate = bool Function(TreeNode node); |
| |
| /// Visitor calling [handle] function on every node which can have metadata |
| /// associated with it and also satisfies the given [predicate]. |
| class Visitor extends RecursiveVisitor { |
| final NodePredicate predicate; |
| final void Function(TreeNode) handle; |
| |
| Visitor(this.predicate, this.handle); |
| |
| @override |
| void defaultTreeNode(TreeNode node) { |
| super.defaultTreeNode(node); |
| if (MetadataRepository.isSupported(node) && predicate(node)) { |
| handle(node); |
| } |
| } |
| } |
| |
| /// Visit the given component assigning [Metadata] object created with |
| /// [Metadata.forNode] to each supported node in the component which matches |
| /// [shouldAnnotate] predicate. |
| void annotate(Component p, NodePredicate shouldAnnotate) { |
| globalDebuggingNames = new NameSystem(); |
| final repository = p.metadata[TestMetadataRepository.kTag]!; |
| p.accept( |
| new Visitor(shouldAnnotate, (node) { |
| repository.mapping[node] = new Metadata.forNode(node); |
| }), |
| ); |
| } |
| |
| /// Visit the given component and checks that each supported node in the |
| /// component matching [shouldAnnotate] predicate has correct metadata. |
| void validate(Component p, NodePredicate shouldAnnotate) { |
| globalDebuggingNames = new NameSystem(); |
| final repository = p.metadata[TestMetadataRepository.kTag]!; |
| p.accept( |
| new Visitor(shouldAnnotate, (node) { |
| final m = repository.mapping[node]; |
| final expected = new Metadata.forNode(node); |
| |
| expect(m, isNotNull); |
| expect(m.string, equals(expected.string)); |
| expect(m.member, equals(expected.member)); |
| expect(m.type, equals(expected.type)); |
| }), |
| ); |
| } |
| |
| Component fromBinary(Uint8List bytes) { |
| var component = new Component(); |
| component.addMetadataRepository(new TestMetadataRepository()); |
| new BinaryBuilderWithMetadata(bytes).readComponent(component); |
| return component; |
| } |
| |
| Uint8List toBinary(Component p) { |
| final sink = new BytesBuilderSink(); |
| new BinaryPrinter(sink).writeComponentFile(p); |
| return sink.builder.takeBytes(); |
| } |
| |
| void main() async { |
| bool anyNode(TreeNode node) => true; |
| bool onlyMethods(TreeNode node) => |
| node is Procedure && |
| node.kind == ProcedureKind.Method && |
| node.enclosingClass != null; |
| |
| final Uri platform = computePlatformBinariesLocation( |
| forceBuildDir: true, |
| ).resolve("vm_platform.dill"); |
| final Uint8List platformBinary = await new File( |
| platform.toFilePath(), |
| ).readAsBytes(); |
| |
| Future<void> testRoundTrip( |
| Uint8List Function(Uint8List) binaryTransformer, |
| NodePredicate shouldAnnotate, |
| ) async { |
| final component = fromBinary(platformBinary); |
| annotate(component, shouldAnnotate); |
| validate(component, shouldAnnotate); |
| expect( |
| component.metadata[TestMetadataRepository.kTag]!.mapping.length, |
| greaterThan(0), |
| ); |
| |
| final annotatedComponentBinary = binaryTransformer(toBinary(component)); |
| final annotatedComponentFromBinary = fromBinary(annotatedComponentBinary); |
| validate(annotatedComponentFromBinary, shouldAnnotate); |
| expect( |
| annotatedComponentFromBinary |
| .metadata[TestMetadataRepository.kTag]! |
| .mapping |
| .length, |
| greaterThan(0), |
| ); |
| } |
| |
| test('annotate-serialize-deserialize-validate', () async { |
| await testRoundTrip((binary) => binary, anyNode); |
| }); |
| |
| test('annotate-serialize-deserialize-validate-only-methods', () async { |
| await testRoundTrip((binary) => binary, onlyMethods); |
| }); |
| |
| test('annotate-serialize-deserialize-twice-then-validate', () async { |
| // This test validates that serializing a component that was just |
| // deserialized (without visiting anything) works. |
| await testRoundTrip((binary) => toBinary(fromBinary(binary)), anyNode); |
| }); |
| |
| test( |
| 'annotate-serialize-deserialize-twice-then-validate-only-methods', |
| () async { |
| // This test validates that serializing a component that was just |
| // deserialized (without visiting anything) works. |
| await testRoundTrip( |
| (binary) => toBinary(fromBinary(binary)), |
| onlyMethods, |
| ); |
| }, |
| ); |
| } |