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 .
This commit is contained in:
Johnni Winther
2016-04-06 11:30:20 +02:00
parent 27c1a6c900
commit ee2fc29142
13 changed files with 305 additions and 37 deletions
+3 -1
View File
@@ -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);
+4 -4
View File
@@ -1959,7 +1959,7 @@ class _CompilerResolution implements Resolution {
final Map<Element, ResolutionImpact> _resolutionImpactCache =
<Element, ResolutionImpact>{};
final Map<Element, WorldImpact> _worldImpactCache = <Element, WorldImpact>{};
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();
}
@@ -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
@@ -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;
@@ -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;
@@ -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;
@@ -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');
@@ -747,7 +747,6 @@ abstract class FunctionTypedElementMixin
}
abstract class ClassElementMixin implements ElementZ, ClassElement {
InterfaceType _createType(List<DartType> 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<DartType> _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
@@ -181,6 +181,8 @@ class ClassHierarchyNode {
_directSubclasses = _directSubclasses.prepend(subclass);
}
Iterable<ClassHierarchyNode> get directSubclasses => _directSubclasses;
/// Returns `true` if [other] is contained in the subtree of this node.
///
/// This means that [other] is a subclass of [cls].
@@ -16,6 +16,10 @@ import 'memory_compiler.dart';
import 'serialization_helper.dart';
const List<Test> TESTS = const <Test>[
const Test(const {
'main.dart': 'main() {}'
}),
const Test(const {
'main.dart': 'main() => print("Hello World");'
}),
@@ -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);
}
}
@@ -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<String> 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<String> 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<String, String> sourceFiles: const <String, String>{},
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);
}
}
+33 -10
View File
@@ -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"