| // 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. |
| |
| // Test that the enqueuers are not dependent upon in which order impacts are |
| // applied. |
| |
| import 'package:compiler/src/compiler.dart'; |
| import 'package:compiler/src/commandline_options.dart'; |
| import 'package:compiler/src/common/elements.dart'; |
| import 'package:compiler/src/elements/entities.dart'; |
| import 'package:compiler/src/elements/names.dart'; |
| import 'package:compiler/src/elements/types.dart'; |
| import 'package:compiler/src/enqueue.dart'; |
| import 'package:compiler/src/inferrer/typemasks/masks.dart'; |
| import 'package:compiler/src/universe/call_structure.dart'; |
| import 'package:compiler/src/universe/selector.dart'; |
| import 'package:compiler/src/universe/world_builder.dart'; |
| import 'package:compiler/src/universe/world_impact.dart'; |
| import 'package:compiler/src/universe/use.dart'; |
| import 'package:compiler/src/js_model/js_world.dart' show JClosedWorld; |
| import 'package:expect/async_helper.dart'; |
| import 'package:expect/expect.dart'; |
| import 'package:compiler/src/util/memory_compiler.dart'; |
| |
| class Test { |
| final String name; |
| final String code; |
| final List<Impact> impacts; |
| final Map<String, List<String>> expectedLiveMap; |
| |
| const Test({ |
| required this.name, |
| required this.code, |
| required this.impacts, |
| required this.expectedLiveMap, |
| }); |
| |
| Map<String, List<String>> get expectedLiveResolutionMap { |
| Map<String, List<String>> map = {}; |
| expectedLiveMap.forEach((String clsName, List<String> memberNames) { |
| for (String memberName in memberNames) { |
| if (memberName.startsWith('?')) { |
| memberName = memberName.substring(1); |
| } |
| map.putIfAbsent(clsName, () => []).add(memberName); |
| } |
| }); |
| return map; |
| } |
| |
| Map<String, List<String>> get expectedLiveCodegenMap { |
| Map<String, List<String>> map = {}; |
| expectedLiveMap.forEach((String clsName, List<String> memberNames) { |
| for (String memberName in memberNames) { |
| if (memberName.startsWith('?')) { |
| // Skip for codegen |
| continue; |
| } |
| map.putIfAbsent(clsName, () => []).add(memberName); |
| } |
| }); |
| return map; |
| } |
| } |
| |
| enum ImpactKind { instantiate, invoke } |
| |
| class Impact { |
| final ImpactKind kind; |
| final String clsName; |
| final String memberName; |
| |
| const Impact.instantiate(this.clsName, [this.memberName = '']) |
| : this.kind = ImpactKind.instantiate; |
| const Impact.invoke(this.clsName, this.memberName) |
| : this.kind = ImpactKind.invoke; |
| |
| @override |
| String toString() => |
| 'Impact(kind=$kind,clsName=$clsName,memberName=$memberName)'; |
| } |
| |
| const List<Test> tests = const <Test>[ |
| const Test( |
| name: 'Instantiate class', |
| code: ''' |
| class A { |
| void method() {} |
| } |
| ''', |
| impacts: const [ |
| const Impact.instantiate('A'), |
| const Impact.invoke('A', 'method'), |
| ], |
| expectedLiveMap: const { |
| 'A': const ['', 'method'], |
| }, |
| ), |
| const Test( |
| name: 'Instantiate subclass', |
| code: ''' |
| class A { |
| void method() {} |
| } |
| class B extends A { |
| } |
| ''', |
| impacts: const [ |
| const Impact.instantiate('B'), |
| const Impact.invoke('B', 'method'), |
| ], |
| expectedLiveMap: const { |
| 'A': const ['?', 'method'], |
| 'B': const [''], |
| }, |
| ), |
| const Test( |
| name: 'Instantiate superclass/subclass', |
| code: ''' |
| class A { |
| void method() {} |
| } |
| class B extends A { |
| } |
| ''', |
| impacts: const [ |
| const Impact.instantiate('A'), |
| const Impact.instantiate('B'), |
| const Impact.invoke('B', 'method'), |
| ], |
| expectedLiveMap: const { |
| 'A': const ['', 'method'], |
| 'B': const [''], |
| }, |
| ), |
| ]; |
| |
| main() { |
| asyncTest(() async { |
| for (Test test in tests) { |
| await runTest(test); |
| } |
| }); |
| } |
| |
| runTest(Test test) async { |
| print('===================================================================='); |
| print('Running test ${test.name}'); |
| for (List<Impact> permutation in permutations(test.impacts)) { |
| print('------------------------------------------------------------------'); |
| print('Permutation: $permutation'); |
| await runTestPermutation(test, permutation); |
| } |
| } |
| |
| Iterable<List<Impact>> permutations(List<Impact> impacts) sync* { |
| int length = impacts.length; |
| if (length <= 1) { |
| yield impacts; |
| } else { |
| for (int index = 0; index < length; index++) { |
| Impact head = impacts[index]; |
| List<Impact> tail = List<Impact>.from(impacts)..removeAt(index); |
| for (List<Impact> permutation in permutations(tail)) { |
| yield [head]..addAll(permutation); |
| } |
| } |
| } |
| } |
| |
| runTestPermutation(Test test, List<Impact> impacts) async { |
| Compiler compiler = compilerFor( |
| memorySourceFiles: { |
| 'main.dart': |
| ''' |
| ${test.code} |
| main() {} |
| ''', |
| }, |
| options: [Flags.disableInlining], |
| entryPoint: Uri.parse('memory:main.dart'), |
| ); |
| |
| void checkInvariant( |
| Enqueuer enqueuer, |
| ElementEnvironment elementEnvironment, |
| ) { |
| for (MemberEntity member in enqueuer.processedEntities) { |
| Expect.isTrue( |
| member == elementEnvironment.mainFunction || |
| member.library != elementEnvironment.mainLibrary, |
| "Unexpected member $member in ${enqueuer}.", |
| ); |
| } |
| } |
| |
| void instantiate( |
| Enqueuer enqueuer, |
| ElementEnvironment elementEnvironment, |
| String name, |
| ) { |
| ClassEntity cls = elementEnvironment.lookupClass( |
| elementEnvironment.mainLibrary!, |
| name, |
| )!; |
| ConstructorEntity constructor = elementEnvironment.lookupConstructor( |
| cls, |
| '', |
| )!; |
| InterfaceType type = elementEnvironment.getRawType(cls); |
| WorldImpact impact = WorldImpactBuilderImpl() |
| ..registerStaticUse( |
| new StaticUse.typedConstructorInvoke( |
| constructor, |
| constructor.parameterStructure.callStructure, |
| type, |
| null, |
| ), |
| ); |
| enqueuer.applyImpact(impact); |
| } |
| |
| void invoke( |
| Enqueuer enqueuer, |
| ElementEnvironment elementEnvironment, |
| String className, |
| String methodName, |
| Object Function(ClassEntity cls) createConstraint, |
| ) { |
| ClassEntity cls = elementEnvironment.lookupClass( |
| elementEnvironment.mainLibrary!, |
| className, |
| )!; |
| Selector selector = Selector.call( |
| Name(methodName, elementEnvironment.mainLibrary!.canonicalUri), |
| CallStructure.noArgs, |
| ); |
| WorldImpact impact = WorldImpactBuilderImpl() |
| ..registerDynamicUse( |
| DynamicUse(selector, createConstraint(cls), const <DartType>[]), |
| ); |
| enqueuer.applyImpact(impact); |
| } |
| |
| void applyImpact( |
| Enqueuer enqueuer, |
| ElementEnvironment elementEnvironment, |
| Impact impact, |
| Object Function(ClassEntity cls) createConstraint, |
| ) { |
| switch (impact.kind) { |
| case ImpactKind.instantiate: |
| instantiate(enqueuer, elementEnvironment, impact.clsName); |
| break; |
| case ImpactKind.invoke: |
| invoke( |
| enqueuer, |
| elementEnvironment, |
| impact.clsName, |
| impact.memberName, |
| createConstraint, |
| ); |
| break; |
| } |
| } |
| |
| void checkLiveMembers( |
| Enqueuer enqueuer, |
| ElementEnvironment elementEnvironment, |
| Map<String, List<String>> expectedLiveMap, |
| ) { |
| Map<String, List<String>> actualLiveMap = {}; |
| for (MemberEntity member in enqueuer.processedEntities) { |
| if (member != elementEnvironment.mainFunction && |
| member.library == elementEnvironment.mainLibrary) { |
| actualLiveMap |
| .putIfAbsent(member.enclosingClass!.name, () => []) |
| .add(member.name!); |
| } |
| } |
| |
| Expect.setEquals( |
| expectedLiveMap.keys, |
| actualLiveMap.keys, |
| "Unexpected live classes in $enqueuer\n " |
| "Expected: ${expectedLiveMap.keys}\n " |
| "Actual : ${actualLiveMap.keys}", |
| ); |
| expectedLiveMap.forEach((String clsName, List<String> expectedMembers) { |
| List<String> actualMembers = actualLiveMap[clsName]!; |
| Expect.setEquals( |
| expectedMembers, |
| actualMembers, |
| "Unexpected live members for $clsName in $enqueuer\n " |
| "Expected: $expectedMembers\n " |
| "Actual : $actualMembers", |
| ); |
| }); |
| } |
| |
| compiler.onResolutionQueueEmptyForTesting = () { |
| Enqueuer enqueuer = compiler.resolutionEnqueuerForTesting; |
| ElementEnvironment elementEnvironment = |
| compiler.frontendStrategy.elementEnvironment; |
| checkInvariant(enqueuer, elementEnvironment); |
| |
| Object createConstraint(ClassEntity cls) { |
| return defaultReceiverClass( |
| compiler.frontendStrategy.commonElements, |
| compiler.frontendStrategy.elementMap.nativeBasicData, |
| cls, |
| ); |
| } |
| |
| for (Impact impact in impacts) { |
| applyImpact(enqueuer, elementEnvironment, impact, createConstraint); |
| } |
| }; |
| compiler.onCodegenQueueEmptyForTesting = () { |
| Enqueuer enqueuer = compiler.codegenEnqueuerForTesting; |
| JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; |
| ElementEnvironment elementEnvironment = |
| compiler.backendClosedWorldForTesting!.elementEnvironment; |
| final domain = closedWorld.abstractValueDomain as CommonMasks; |
| checkInvariant(enqueuer, elementEnvironment); |
| |
| Object createConstraint(ClassEntity cls) { |
| return TypeMask.subtype(cls, domain); |
| } |
| |
| for (Impact impact in impacts) { |
| applyImpact(enqueuer, elementEnvironment, impact, createConstraint); |
| } |
| }; |
| |
| await compiler.run(); |
| |
| checkLiveMembers( |
| compiler.resolutionEnqueuerForTesting, |
| compiler.frontendStrategy.elementEnvironment, |
| test.expectedLiveResolutionMap, |
| ); |
| |
| checkLiveMembers( |
| compiler.codegenEnqueuerForTesting, |
| compiler.backendClosedWorldForTesting!.elementEnvironment, |
| test.expectedLiveCodegenMap, |
| ); |
| } |