From ee2fc29142835fa93dcd875ee948ccdb53ffe13e Mon Sep 17 00:00:00 2001 From: Johnni Winther Date: Wed, 6 Apr 2016 11:30:20 +0200 Subject: [PATCH] Test closed world model after deserialization. We still need better tracking of native/foreign elements. R=sigmund@google.com Review URL: https://codereview.chromium.org/1856953003 . --- pkg/compiler/lib/src/common/resolution.dart | 4 +- pkg/compiler/lib/src/compiler.dart | 8 +- pkg/compiler/lib/src/elements/common.dart | 8 + pkg/compiler/lib/src/elements/modelx.dart | 7 - .../serialization/element_serialization.dart | 5 +- .../lib/src/serialization/equivalence.dart | 1 - pkg/compiler/lib/src/serialization/keys.dart | 1 + .../lib/src/serialization/modelz.dart | 14 +- pkg/compiler/lib/src/universe/class_set.dart | 2 + .../dart2js/serialization_analysis_test.dart | 4 + .../dart2js/serialization_impact_test.dart | 21 +- .../dart2js/serialization_model_test.dart | 224 ++++++++++++++++++ .../compiler/dart2js/serialization_test.dart | 43 +++- 13 files changed, 305 insertions(+), 37 deletions(-) create mode 100644 tests/compiler/dart2js/serialization_model_test.dart diff --git a/pkg/compiler/lib/src/common/resolution.dart b/pkg/compiler/lib/src/common/resolution.dart index b7822d7e5a7..7c675afe941 100644 --- a/pkg/compiler/lib/src/common/resolution.dart +++ b/pkg/compiler/lib/src/common/resolution.dart @@ -188,7 +188,9 @@ abstract class Resolution { DiagnosticReporter get reporter; CoreTypes get coreTypes; - bool retainCaches; + /// If set to `true` resolution caches will not be cleared. Use this only for + /// testing. + bool retainCachesForTesting; void resolveTypedef(TypedefElement typdef); void resolveClass(ClassElement cls); diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart index 39fc9b9bcb4..2cfadd34dbf 100644 --- a/pkg/compiler/lib/src/compiler.dart +++ b/pkg/compiler/lib/src/compiler.dart @@ -1959,7 +1959,7 @@ class _CompilerResolution implements Resolution { final Map _resolutionImpactCache = {}; final Map _worldImpactCache = {}; - bool retainCaches = false; + bool retainCachesForTesting = false; _CompilerResolution(this.compiler); @@ -2031,7 +2031,7 @@ class _CompilerResolution implements Resolution { assert(invariant(element, !element.isSynthesized || tree == null)); ResolutionImpact resolutionImpact = compiler.resolver.resolve(element); - if (compiler.serialization.supportSerialization || retainCaches) { + if (compiler.serialization.supportSerialization || retainCachesForTesting) { // [ResolutionImpact] is currently only used by serialization. The // enqueuer uses the [WorldImpact] which is always cached. // TODO(johnniwinther): Align these use cases better; maybe only @@ -2054,7 +2054,7 @@ class _CompilerResolution implements Resolution { @override void uncacheWorldImpact(Element element) { - if (retainCaches) return; + if (retainCachesForTesting) return; if (compiler.serialization.isDeserialized(element)) return; assert(invariant(element, _worldImpactCache[element] != null, message: "WorldImpact not computed for $element.")); @@ -2064,7 +2064,7 @@ class _CompilerResolution implements Resolution { @override void emptyCache() { - if (retainCaches) return; + if (retainCachesForTesting) return; for (Element element in _worldImpactCache.keys) { _worldImpactCache[element] = const WorldImpact(); } diff --git a/pkg/compiler/lib/src/elements/common.dart b/pkg/compiler/lib/src/elements/common.dart index 6c2e843b599..85fd159df1e 100644 --- a/pkg/compiler/lib/src/elements/common.dart +++ b/pkg/compiler/lib/src/elements/common.dart @@ -9,6 +9,8 @@ library elements.common; import '../common/names.dart' show Names, Uris; +import '../core_types.dart' show + CoreClasses; import '../dart_types.dart' show DartType, InterfaceType, @@ -429,6 +431,12 @@ abstract class ClassElementCommon implements ClassElement { allSupertypesAndSelf.asInstanceOf(intrface) != null; } + @override + bool implementsFunction(CoreClasses coreClasses) { + return asInstanceOf(coreClasses.functionClass) != null || + callType != null; + } + @override bool isSubclassOf(ClassElement cls) { // Use [declaration] for both [this] and [cls], because diff --git a/pkg/compiler/lib/src/elements/modelx.dart b/pkg/compiler/lib/src/elements/modelx.dart index be8b45cf97e..bed7ceab5d7 100644 --- a/pkg/compiler/lib/src/elements/modelx.dart +++ b/pkg/compiler/lib/src/elements/modelx.dart @@ -13,8 +13,6 @@ import '../compiler.dart' show import '../constants/constant_constructors.dart'; import '../constants/constructors.dart'; import '../constants/expressions.dart'; -import '../core_types.dart' show - CoreClasses; import '../dart_types.dart'; import '../diagnostics/messages.dart' show MessageTemplate; @@ -2701,11 +2699,6 @@ abstract class BaseClassElementX extends ElementX backendMembers.forEach(f); } - bool implementsFunction(CoreClasses coreClasses) { - return asInstanceOf(coreClasses.functionClass) != null || - callType != null; - } - // TODO(johnniwinther): Remove these when issue 18630 is fixed. ClassElement get patch => super.patch; ClassElement get origin => super.origin; diff --git a/pkg/compiler/lib/src/serialization/element_serialization.dart b/pkg/compiler/lib/src/serialization/element_serialization.dart index d9877a47400..86c3337ac57 100644 --- a/pkg/compiler/lib/src/serialization/element_serialization.dart +++ b/pkg/compiler/lib/src/serialization/element_serialization.dart @@ -308,10 +308,13 @@ class ClassSerializer implements ElementSerializer { mixins = mixins.reversed.toList(); InterfaceType supertype = element.thisType.asInstanceOf(superclass); - encoder.setType(Key.SUPERTYPE, supertype); encoder.setTypes(Key.MIXINS, mixins); encoder.setTypes(Key.INTERFACES, element.interfaces.toList()); + FunctionType callType = element.declaration.callType; + if (callType != null) { + encoder.setType(Key.CALL_TYPE, element.callType); + } if (element.isMixinApplication) { MixinApplicationElement mixinElement = element; diff --git a/pkg/compiler/lib/src/serialization/equivalence.dart b/pkg/compiler/lib/src/serialization/equivalence.dart index c91561b2a3d..5bc9d23bb8b 100644 --- a/pkg/compiler/lib/src/serialization/equivalence.dart +++ b/pkg/compiler/lib/src/serialization/equivalence.dart @@ -39,7 +39,6 @@ bool areSetsEquivalent( Iterable set1, Iterable set2, [bool elementEquivalence(a, b) = equality]) { - Set remaining = set2.toSet(); for (var element1 in set1) { bool found = false; diff --git a/pkg/compiler/lib/src/serialization/keys.dart b/pkg/compiler/lib/src/serialization/keys.dart index 9ee569dc003..7758be67bdb 100644 --- a/pkg/compiler/lib/src/serialization/keys.dart +++ b/pkg/compiler/lib/src/serialization/keys.dart @@ -10,6 +10,7 @@ class Key { static const Key ARGUMENTS = const Key('arguments'); static const Key BOUND = const Key('bound'); static const Key CALL_STRUCTURE = const Key('callStructure'); + static const Key CALL_TYPE = const Key('callType'); static const Key CANONICAL_URI = const Key('canonicalUri'); static const Key CLASS = const Key('class'); static const Key COMPILATION_UNIT = const Key('compilation-unit'); diff --git a/pkg/compiler/lib/src/serialization/modelz.dart b/pkg/compiler/lib/src/serialization/modelz.dart index dc6ede2ad78..7c02c7a07ff 100644 --- a/pkg/compiler/lib/src/serialization/modelz.dart +++ b/pkg/compiler/lib/src/serialization/modelz.dart @@ -747,7 +747,6 @@ abstract class FunctionTypedElementMixin } abstract class ClassElementMixin implements ElementZ, ClassElement { - InterfaceType _createType(List typeArguments) { return new InterfaceType(this, typeArguments); } @@ -778,11 +777,6 @@ abstract class ClassElementMixin implements ElementZ, ClassElement { @override bool get hasLocalScopeMembers => _unsupported('hasLocalScopeMembers'); - @override - bool implementsFunction(CoreClasses coreClasses) { - return _unsupported('implementsFunction'); - } - @override bool get isEnumClass => false; @@ -827,6 +821,7 @@ class ClassElementZ extends DeserializedElementZ DartType _supertype; OrderedTypeSet _allSupertypesAndSelf; Link _interfaces; + FunctionType _callType; ClassElementZ(ObjectDecoder decoder) : super(decoder); @@ -860,6 +855,7 @@ class ClassElementZ extends DeserializedElementZ _allSupertypesAndSelf = new OrderedTypeSetBuilder(this) .createOrderedTypeSet(_supertype, _interfaces); + _callType = _decoder.getType(Key.CALL_TYPE, isOptional: true); } } } @@ -901,6 +897,12 @@ class ClassElementZ extends DeserializedElementZ @override bool get isUnnamedMixinApplication => false; + + @override + FunctionType get callType { + _ensureSuperHierarchy(); + return _callType; + } } abstract class MixinApplicationElementMixin diff --git a/pkg/compiler/lib/src/universe/class_set.dart b/pkg/compiler/lib/src/universe/class_set.dart index 818a6d7d3cb..bed1d6065a7 100644 --- a/pkg/compiler/lib/src/universe/class_set.dart +++ b/pkg/compiler/lib/src/universe/class_set.dart @@ -181,6 +181,8 @@ class ClassHierarchyNode { _directSubclasses = _directSubclasses.prepend(subclass); } + Iterable get directSubclasses => _directSubclasses; + /// Returns `true` if [other] is contained in the subtree of this node. /// /// This means that [other] is a subclass of [cls]. diff --git a/tests/compiler/dart2js/serialization_analysis_test.dart b/tests/compiler/dart2js/serialization_analysis_test.dart index ebbc6f0c423..373fb89e5bb 100644 --- a/tests/compiler/dart2js/serialization_analysis_test.dart +++ b/tests/compiler/dart2js/serialization_analysis_test.dart @@ -16,6 +16,10 @@ import 'memory_compiler.dart'; import 'serialization_helper.dart'; const List TESTS = const [ + const Test(const { + 'main.dart': 'main() {}' + }), + const Test(const { 'main.dart': 'main() => print("Hello World");' }), diff --git a/tests/compiler/dart2js/serialization_impact_test.dart b/tests/compiler/dart2js/serialization_impact_test.dart index 0bd48338f8f..2fbd212a172 100644 --- a/tests/compiler/dart2js/serialization_impact_test.dart +++ b/tests/compiler/dart2js/serialization_impact_test.dart @@ -37,25 +37,28 @@ Future check( Compiler compilerNormal = compilerFor( memorySourceFiles: sourceFiles, options: [Flags.analyzeOnly]); - compilerNormal.resolution.retainCaches = true; + compilerNormal.resolution.retainCachesForTesting = true; await compilerNormal.run(entryPoint); Compiler compilerDeserialized = compilerFor( memorySourceFiles: sourceFiles, options: [Flags.analyzeOnly]); - compilerDeserialized.resolution.retainCaches = true; + compilerDeserialized.resolution.retainCachesForTesting = true; deserialize(compilerDeserialized, serializedData); await compilerDeserialized.run(entryPoint); - checkResolutionImpacts(compilerNormal, compilerDeserialized); + checkResolutionImpacts(compilerNormal, compilerDeserialized, verbose: true); } /// Check equivalence of [impact1] and [impact2]. void checkImpacts(Element element1, Element element2, - ResolutionImpact impact1, ResolutionImpact impact2) { + ResolutionImpact impact1, ResolutionImpact impact2, + {bool verbose: false}) { if (impact1 == null || impact2 == null) return; - print('Checking impacts for $element1 vs $element2'); + if (verbose) { + print('Checking impacts for $element1 vs $element2'); + } testResolutionImpactEquivalence(impact1, impact2, const CheckStrategy()); } @@ -63,7 +66,10 @@ void checkImpacts(Element element1, Element element2, /// Check equivalence between all resolution impacts common to [compiler1] and /// [compiler2]. -void checkResolutionImpacts(Compiler compiler1, Compiler compiler2) { +void checkResolutionImpacts( + Compiler compiler1, + Compiler compiler2, + {bool verbose: false}) { void checkMembers(Element member1, Element member2) { if (member1.isClass && member2.isClass) { @@ -87,7 +93,8 @@ void checkResolutionImpacts(Compiler compiler1, Compiler compiler2) { checkImpacts( member1, member2, compiler1.resolution.getResolutionImpact(member1), - compiler2.serialization.deserializer.getResolutionImpact(member2)); + compiler2.serialization.deserializer.getResolutionImpact(member2), + verbose: verbose); } } diff --git a/tests/compiler/dart2js/serialization_model_test.dart b/tests/compiler/dart2js/serialization_model_test.dart new file mode 100644 index 00000000000..b8cef2598d3 --- /dev/null +++ b/tests/compiler/dart2js/serialization_model_test.dart @@ -0,0 +1,224 @@ +// 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. + +library dart2js.serialization_model_test; + +import 'dart:async'; +import 'dart:io'; +import 'package:async_helper/async_helper.dart'; +import 'package:expect/expect.dart'; +import 'package:compiler/src/commandline_options.dart'; +import 'package:compiler/src/common/backend_api.dart'; +import 'package:compiler/src/common/names.dart'; +import 'package:compiler/src/common/resolution.dart'; +import 'package:compiler/src/compiler.dart'; +import 'package:compiler/src/dart_types.dart'; +import 'package:compiler/src/elements/elements.dart'; +import 'package:compiler/src/filenames.dart'; +import 'package:compiler/src/serialization/element_serialization.dart'; +import 'package:compiler/src/serialization/impact_serialization.dart'; +import 'package:compiler/src/serialization/json_serializer.dart'; +import 'package:compiler/src/serialization/serialization.dart'; +import 'package:compiler/src/serialization/equivalence.dart'; +import 'package:compiler/src/serialization/task.dart'; +import 'package:compiler/src/universe/world_impact.dart'; +import 'package:compiler/src/universe/class_set.dart'; +import 'package:compiler/src/universe/use.dart'; +import 'memory_compiler.dart'; +import 'serialization_helper.dart'; +import 'serialization_analysis_test.dart'; +import 'serialization_impact_test.dart'; +import 'serialization_test.dart'; + +main(List arguments) { + String filename; + for (String arg in arguments) { + if (!arg.startsWith('-')) { + filename = arg; + } + } + bool verbose = arguments.contains('-v'); + + asyncTest(() async { + print('------------------------------------------------------------------'); + print('serialize dart:core'); + print('------------------------------------------------------------------'); + String serializedData; + File file = new File('out.data'); + if (arguments.contains('-l')) { + if (file.existsSync()) { + print('Loading data from $file'); + serializedData = file.readAsStringSync(); + } + } + if (serializedData == null) { + serializedData = await serializeDartCore(); + if (arguments.contains('-s')) { + print('Saving data to $file'); + file.writeAsStringSync(serializedData); + } + } + if (filename != null) { + Uri entryPoint = Uri.base.resolve(nativeToUriPath(filename)); + await check(serializedData, entryPoint); + } else { + Uri entryPoint = Uri.parse('memory:main.dart'); + for (Test test in TESTS) { + if (test.sourceFiles['main.dart'] + .contains('main(List arguments)')) { + // TODO(johnniwinther): Check this test. + continue; + } + print('=============================================================='); + print(test.sourceFiles); + await check( + serializedData, + entryPoint, + sourceFiles: test.sourceFiles, + verbose: verbose); + } + } + }); +} + +Future check( + String serializedData, + Uri entryPoint, + {Map sourceFiles: const {}, + bool verbose: false}) async { + + print('------------------------------------------------------------------'); + print('compile normal'); + print('------------------------------------------------------------------'); + Compiler compilerNormal = compilerFor( + memorySourceFiles: sourceFiles, + options: [Flags.analyzeOnly]); + compilerNormal.resolution.retainCachesForTesting = true; + await compilerNormal.run(entryPoint); + compilerNormal.world.populate(); + + print('------------------------------------------------------------------'); + print('compile deserialized'); + print('------------------------------------------------------------------'); + Compiler compilerDeserialized = compilerFor( + memorySourceFiles: sourceFiles, + options: [Flags.analyzeOnly]); + compilerDeserialized.resolution.retainCachesForTesting = true; + deserialize(compilerDeserialized, serializedData); + await compilerDeserialized.run(entryPoint); + compilerDeserialized.world.populate(); + + checkResolutionImpacts( + compilerNormal, compilerDeserialized, + verbose: verbose); + + checkSets( + compilerNormal.resolverWorld.directlyInstantiatedClasses, + compilerDeserialized.resolverWorld.directlyInstantiatedClasses, + "Directly instantiated classes mismatch", + areElementsEquivalent, + verbose: verbose); + + checkSets( + compilerNormal.resolverWorld.instantiatedTypes, + compilerDeserialized.resolverWorld.instantiatedTypes, + "Instantiated types mismatch", + areTypesEquivalent, + // TODO(johnniwinther): Ensure that all instantiated types are tracked. + failOnUnfound: false, + verbose: verbose); + + checkSets( + compilerNormal.resolverWorld.isChecks, + compilerDeserialized.resolverWorld.isChecks, + "Is-check mismatch", + areTypesEquivalent, + verbose: verbose); + + checkSets( + compilerNormal.enqueuer.resolution.processedElements, + compilerDeserialized.enqueuer.resolution.processedElements, + "Processed element mismatch", + areElementsEquivalent, + verbose: verbose); + + checkClassHierarchyNodes( + compilerNormal.world.getClassHierarchyNode( + compilerNormal.coreClasses.objectClass), + compilerDeserialized.world.getClassHierarchyNode( + compilerDeserialized.coreClasses.objectClass), + verbose: verbose); +} + +void checkClassHierarchyNodes( + ClassHierarchyNode a, ClassHierarchyNode b, + {bool verbose: false}) { + if (verbose) { + print('Checking $a vs $b'); + } + Expect.isTrue( + areElementsEquivalent(a.cls, b.cls), + "Element identity mismatch for ${a.cls} vs ${b.cls}."); + Expect.equals( + a.isDirectlyInstantiated, + b.isDirectlyInstantiated, + "Value mismatch for 'isDirectlyInstantiated' for ${a.cls} vs ${b.cls}."); + Expect.equals( + a.isIndirectlyInstantiated, + b.isIndirectlyInstantiated, + "Value mismatch for 'isIndirectlyInstantiated' " + "for ${a.cls} vs ${b.cls}."); + // TODO(johnniwinther): Enforce a canonical and stable order on direct + // subclasses. + for (ClassHierarchyNode child in a.directSubclasses) { + bool found = false; + for (ClassHierarchyNode other in b.directSubclasses) { + if (areElementsEquivalent(child.cls, other.cls)) { + checkClassHierarchyNodes(child, other, + verbose: verbose); + found = true; + break; + } + } + if (!found) { + Expect.isFalse( + child.isInstantiated, 'Missing subclass ${child.cls} of ${a.cls}'); + } + } +} + +void checkSets( + Iterable set1, + Iterable set2, + String messagePrefix, + bool areEquivalent(a, b), + {bool failOnUnfound: true, + bool verbose: false}) { + List common = []; + List unfound = []; + Set remaining = computeSetDifference( + set1, set2, common, unfound, areEquivalent); + StringBuffer sb = new StringBuffer(); + sb.write("$messagePrefix:"); + if (verbose) { + sb.write("\n Common:\n ${common.join('\n ')}"); + } + if (unfound.isNotEmpty || verbose) { + sb.write("\n Unfound:\n ${unfound.join('\n ')}"); + } + if (remaining.isNotEmpty || verbose) { + sb.write("\n Extra: \n ${remaining.join('\n ')}"); + } + String message = sb.toString(); + if (unfound.isNotEmpty || remaining.isNotEmpty) { + + if (failOnUnfound || remaining.isNotEmpty) { + Expect.fail(message); + } else { + print(message); + } + } else if (verbose) { + print(message); + } +} diff --git a/tests/compiler/dart2js/serialization_test.dart b/tests/compiler/dart2js/serialization_test.dart index 26740bc477e..c19fb614529 100644 --- a/tests/compiler/dart2js/serialization_test.dart +++ b/tests/compiler/dart2js/serialization_test.dart @@ -171,19 +171,24 @@ bool checkListEquivalence( return true; } -/// Check equivalence of the two iterables, [set1] and [set1], as sets using -/// [elementEquivalence] to compute the pair-wise equivalence. +/// Computes the set difference between [set1] and [set2] using +/// [elementEquivalence] to determine element equivalence. /// -/// Uses [object1], [object2] and [property] to provide context for failures. -bool checkSetEquivalence( - var object1, - var object2, - String property, +/// Elements both in [set1] and [set2] are added to [common], elements in [set1] +/// but not in [set2] are added to [unfound], and the set of elements in [set2] +/// but not in [set1] are returned. +Set computeSetDifference( Iterable set1, Iterable set2, - bool sameElement(a, b)) { - List common = []; - List unfound = []; + List common, + List unfound, + [bool sameElement(a, b) = equality]) { + // TODO(johnniwinther): Avoid the quadratic cost here. Some ideas: + // - convert each set to a list and sort it first, then compare by walking + // both lists in parallel + // - map each element to a canonical object, create a map containing those + // mappings, use the mapped sets to compare (then operations like + // set.difference would work) Set remaining = set2.toSet(); for (var element1 in set1) { bool found = false; @@ -200,6 +205,24 @@ bool checkSetEquivalence( unfound.add(element1); } } + return remaining; +} + +/// Check equivalence of the two iterables, [set1] and [set1], as sets using +/// [elementEquivalence] to compute the pair-wise equivalence. +/// +/// Uses [object1], [object2] and [property] to provide context for failures. +bool checkSetEquivalence( + var object1, + var object2, + String property, + Iterable set1, + Iterable set2, + bool sameElement(a, b)) { + List common = []; + List unfound = []; + Set remaining = + computeSetDifference(set1, set2, common, unfound, sameElement); if (unfound.isNotEmpty || remaining.isNotEmpty) { String message = "Set mismatch for `$property` on $object1 vs $object2: \n"