| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dart2js.serialization_test_helper; | 5 /// General equivalence test functions. |
| 6 | 6 |
| 7 import 'dart:collection'; | 7 library dart2js.equivalence.helpers; |
| 8 import 'package:compiler/src/common/resolution.dart'; | 8 |
| 9 import 'package:compiler/src/constants/expressions.dart'; | 9 import 'package:compiler/src/constants/expressions.dart'; |
| 10 import 'package:compiler/src/constants/values.dart'; | 10 import 'package:compiler/src/constants/values.dart'; |
| 11 import 'package:compiler/src/compiler.dart'; | |
| 12 import 'package:compiler/src/elements/elements.dart'; | 11 import 'package:compiler/src/elements/elements.dart'; |
| 13 import 'package:compiler/src/elements/entities.dart'; | 12 import 'package:compiler/src/elements/entities.dart'; |
| 14 import 'package:compiler/src/elements/resolution_types.dart'; | 13 import 'package:compiler/src/elements/resolution_types.dart'; |
| 15 import 'package:compiler/src/elements/types.dart'; | 14 import 'package:compiler/src/elements/types.dart'; |
| 16 import 'package:compiler/src/kernel/elements.dart'; | |
| 17 import 'package:compiler/src/kernel/element_map_impl.dart'; | |
| 18 import 'package:compiler/src/serialization/equivalence.dart'; | 15 import 'package:compiler/src/serialization/equivalence.dart'; |
| 19 import 'package:compiler/src/util/util.dart'; | |
| 20 import 'package:expect/expect.dart'; | 16 import 'package:expect/expect.dart'; |
| 21 import 'test_data.dart'; | |
| 22 | 17 |
| 23 Check currentCheck; | 18 Check currentCheck; |
| 24 | 19 |
| 25 class Check { | 20 class Check { |
| 26 final Check parent; | 21 final Check parent; |
| 27 final Object object1; | 22 final Object object1; |
| 28 final Object object2; | 23 final Object object2; |
| 29 final String property; | 24 final String property; |
| 30 final Object value1; | 25 final Object value1; |
| 31 final Object value2; | 26 final Object value2; |
| (...skipping 307 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 339 /// Checks the pair-wise equivalence of the constants values in [list1] and | 334 /// Checks the pair-wise equivalence of the constants values in [list1] and |
| 340 /// [list2]. | 335 /// [list2]. |
| 341 /// | 336 /// |
| 342 /// Uses [object1], [object2] and [property] to provide context for failures. | 337 /// Uses [object1], [object2] and [property] to provide context for failures. |
| 343 bool checkConstantValueLists(Object object1, Object object2, String property, | 338 bool checkConstantValueLists(Object object1, Object object2, String property, |
| 344 List<ConstantValue> list1, List<ConstantValue> list2) { | 339 List<ConstantValue> list1, List<ConstantValue> list2) { |
| 345 return checkListEquivalence( | 340 return checkListEquivalence( |
| 346 object1, object2, property, list1, list2, checkConstantValues); | 341 object1, object2, property, list1, list2, checkConstantValues); |
| 347 } | 342 } |
| 348 | 343 |
| 349 /// Check member property equivalence between all members common to [compiler1] | |
| 350 /// and [compiler2]. | |
| 351 void checkLoadedLibraryMembers( | |
| 352 Compiler compiler1, | |
| 353 Compiler compiler2, | |
| 354 bool hasProperty(Element member1), | |
| 355 void checkMemberProperties(Compiler compiler1, Element member1, | |
| 356 Compiler compiler2, Element member2, | |
| 357 {bool verbose}), | |
| 358 {bool verbose: false}) { | |
| 359 void checkMembers(Element member1, Element member2) { | |
| 360 if (member1.isClass && member2.isClass) { | |
| 361 ClassElement class1 = member1; | |
| 362 ClassElement class2 = member2; | |
| 363 if (!class1.isResolved) return; | |
| 364 | |
| 365 if (hasProperty(member1)) { | |
| 366 if (areElementsEquivalent(member1, member2)) { | |
| 367 checkMemberProperties(compiler1, member1, compiler2, member2, | |
| 368 verbose: verbose); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 class1.forEachLocalMember((m1) { | |
| 373 checkMembers(m1, class2.localLookup(m1.name)); | |
| 374 }); | |
| 375 ClassElement superclass1 = class1.superclass; | |
| 376 ClassElement superclass2 = class2.superclass; | |
| 377 while (superclass1 != null && superclass1.isUnnamedMixinApplication) { | |
| 378 for (ConstructorElement c1 in superclass1.constructors) { | |
| 379 checkMembers(c1, superclass2.lookupConstructor(c1.name)); | |
| 380 } | |
| 381 superclass1 = superclass1.superclass; | |
| 382 superclass2 = superclass2.superclass; | |
| 383 } | |
| 384 return; | |
| 385 } | |
| 386 | |
| 387 if (!hasProperty(member1)) { | |
| 388 return; | |
| 389 } | |
| 390 | |
| 391 if (member2 == null) { | |
| 392 throw 'Missing member for ${member1}'; | |
| 393 } | |
| 394 | |
| 395 if (areElementsEquivalent(member1, member2)) { | |
| 396 checkMemberProperties(compiler1, member1, compiler2, member2, | |
| 397 verbose: verbose); | |
| 398 } | |
| 399 } | |
| 400 | |
| 401 for (LibraryElement library1 in compiler1.libraryLoader.libraries) { | |
| 402 LibraryElement library2 = | |
| 403 compiler2.libraryLoader.lookupLibrary(library1.canonicalUri); | |
| 404 if (library2 != null) { | |
| 405 library1.forEachLocalMember((Element member1) { | |
| 406 checkMembers(member1, library2.localLookup(member1.name)); | |
| 407 }); | |
| 408 } | |
| 409 } | |
| 410 } | |
| 411 | |
| 412 /// Check equivalence of all resolution impacts. | |
| 413 void checkAllImpacts(Compiler compiler1, Compiler compiler2, | |
| 414 {bool verbose: false}) { | |
| 415 checkLoadedLibraryMembers(compiler1, compiler2, (Element member1) { | |
| 416 return compiler1.resolution.hasResolutionImpact(member1); | |
| 417 }, checkImpacts, verbose: verbose); | |
| 418 } | |
| 419 | |
| 420 /// Check equivalence of resolution impact for [member1] and [member2]. | |
| 421 void checkImpacts( | |
| 422 Compiler compiler1, Element member1, Compiler compiler2, Element member2, | |
| 423 {bool verbose: false}) { | |
| 424 ResolutionImpact impact1 = compiler1.resolution.getResolutionImpact(member1); | |
| 425 ResolutionImpact impact2 = compiler2.resolution.getResolutionImpact(member2); | |
| 426 | |
| 427 if (impact1 == null && impact2 == null) return; | |
| 428 | |
| 429 if (verbose) { | |
| 430 print('Checking impacts for $member1 vs $member2'); | |
| 431 } | |
| 432 | |
| 433 if (impact1 == null) { | |
| 434 throw 'Missing impact for $member1. $member2 has $impact2'; | |
| 435 } | |
| 436 if (impact2 == null) { | |
| 437 throw 'Missing impact for $member2. $member1 has $impact1'; | |
| 438 } | |
| 439 | |
| 440 testResolutionImpactEquivalence(impact1, impact2, | |
| 441 strategy: const CheckStrategy()); | |
| 442 } | |
| 443 | |
| 444 void checkSets( | 344 void checkSets( |
| 445 Iterable set1, Iterable set2, String messagePrefix, bool sameElement(a, b), | 345 Iterable set1, Iterable set2, String messagePrefix, bool sameElement(a, b), |
| 446 {bool failOnUnfound: true, | 346 {bool failOnUnfound: true, |
| 447 bool failOnExtra: true, | 347 bool failOnExtra: true, |
| 448 bool verbose: false, | 348 bool verbose: false, |
| 449 void onSameElement(a, b), | 349 void onSameElement(a, b), |
| 450 void onUnfoundElement(a), | 350 void onUnfoundElement(a), |
| 451 void onExtraElement(b), | 351 void onExtraElement(b), |
| 452 bool elementFilter(element), | 352 bool elementFilter(element), |
| 453 elementConverter(element), | 353 elementConverter(element), |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 570 (mismatch.isNotEmpty && failOnMismatch) || | 470 (mismatch.isNotEmpty && failOnMismatch) || |
| 571 remaining.isNotEmpty) { | 471 remaining.isNotEmpty) { |
| 572 Expect.fail(message); | 472 Expect.fail(message); |
| 573 } else { | 473 } else { |
| 574 print(message); | 474 print(message); |
| 575 } | 475 } |
| 576 } else if (verbose) { | 476 } else if (verbose) { |
| 577 print(message); | 477 print(message); |
| 578 } | 478 } |
| 579 } | 479 } |
| 580 | |
| 581 void checkAllResolvedAsts(Compiler compiler1, Compiler compiler2, | |
| 582 {bool verbose: false}) { | |
| 583 checkLoadedLibraryMembers(compiler1, compiler2, (Element member1) { | |
| 584 return member1 is ExecutableElement && | |
| 585 compiler1.resolution.hasResolvedAst(member1); | |
| 586 }, checkResolvedAsts, verbose: verbose); | |
| 587 } | |
| 588 | |
| 589 /// Check equivalence of [impact1] and [impact2]. | |
| 590 void checkResolvedAsts( | |
| 591 Compiler compiler1, Element member1, Compiler compiler2, Element member2, | |
| 592 {bool verbose: false}) { | |
| 593 if (!compiler2.serialization.isDeserialized(member2)) { | |
| 594 return; | |
| 595 } | |
| 596 ResolvedAst resolvedAst1 = compiler1.resolution.getResolvedAst(member1); | |
| 597 ResolvedAst resolvedAst2 = compiler2.serialization.getResolvedAst(member2); | |
| 598 | |
| 599 if (resolvedAst1 == null || resolvedAst2 == null) return; | |
| 600 | |
| 601 if (verbose) { | |
| 602 print('Checking resolved asts for $member1 vs $member2'); | |
| 603 } | |
| 604 | |
| 605 testResolvedAstEquivalence(resolvedAst1, resolvedAst2, const CheckStrategy()); | |
| 606 } | |
| 607 | |
| 608 /// Returns the test arguments for testing the [index]th skipped test. The | |
| 609 /// [skip] count is used to check that [index] is a valid index. | |
| 610 List<String> testSkipped(int index, int skip) { | |
| 611 if (index < 0 || index >= skip) { | |
| 612 throw new ArgumentError('Invalid skip index $index'); | |
| 613 } | |
| 614 return ['${index}', '${index + 1}']; | |
| 615 } | |
| 616 | |
| 617 /// Return the test arguments for testing the [index]th segment (1-based) of | |
| 618 /// the [TESTS] split into [count] groups. The first [skip] tests are excluded | |
| 619 /// from the automatic grouping. | |
| 620 List<String> testSegment(int index, int count, int skip) { | |
| 621 if (index < 0 || index > count) { | |
| 622 throw new ArgumentError('Invalid segment index $index'); | |
| 623 } | |
| 624 | |
| 625 String segmentNumber(int i) { | |
| 626 return '${skip + i * (TESTS.length - skip) ~/ count}'; | |
| 627 } | |
| 628 | |
| 629 if (index == 1 && skip != 0) { | |
| 630 return ['${skip}', segmentNumber(index)]; | |
| 631 } else if (index == count) { | |
| 632 return [segmentNumber(index - 1)]; | |
| 633 } else { | |
| 634 return [segmentNumber(index - 1), segmentNumber(index)]; | |
| 635 } | |
| 636 } | |
| 637 | |
| 638 class KernelEquivalence { | |
| 639 final WorldDeconstructionForTesting testing; | |
| 640 | |
| 641 /// Set of mixin applications assumed to be equivalent. | |
| 642 /// | |
| 643 /// We need co-inductive reasoning because mixin applications are compared | |
| 644 /// structurally and therefore, in the case of generic mixin applications, | |
| 645 /// meet themselves through the equivalence check of their type variables. | |
| 646 Set<Pair<ClassEntity, ClassEntity>> assumedMixinApplications = | |
| 647 new Set<Pair<ClassEntity, ClassEntity>>(); | |
| 648 | |
| 649 KernelEquivalence(KernelToElementMapImpl builder) | |
| 650 : testing = new WorldDeconstructionForTesting(builder); | |
| 651 | |
| 652 TestStrategy get defaultStrategy => new TestStrategy( | |
| 653 elementEquivalence: entityEquivalence, | |
| 654 typeEquivalence: typeEquivalence, | |
| 655 constantEquivalence: constantEquivalence, | |
| 656 constantValueEquivalence: constantValueEquivalence); | |
| 657 | |
| 658 bool entityEquivalence(Element a, Entity b, {TestStrategy strategy}) { | |
| 659 if (identical(a, b)) return true; | |
| 660 if (a == null || b == null) return false; | |
| 661 strategy ??= defaultStrategy; | |
| 662 switch (a.kind) { | |
| 663 case ElementKind.GENERATIVE_CONSTRUCTOR: | |
| 664 if (b is KGenerativeConstructor) { | |
| 665 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 666 strategy.testElements( | |
| 667 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass); | |
| 668 } | |
| 669 return false; | |
| 670 case ElementKind.FACTORY_CONSTRUCTOR: | |
| 671 if (b is KFactoryConstructor) { | |
| 672 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 673 strategy.testElements( | |
| 674 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass); | |
| 675 } | |
| 676 return false; | |
| 677 case ElementKind.CLASS: | |
| 678 if (b is KClass) { | |
| 679 List<InterfaceType> aMixinTypes = []; | |
| 680 List<InterfaceType> bMixinTypes = []; | |
| 681 ClassElement aClass = a; | |
| 682 if (aClass.isUnnamedMixinApplication) { | |
| 683 if (!testing.isUnnamedMixinApplication(b)) { | |
| 684 return false; | |
| 685 } | |
| 686 while (aClass.isMixinApplication) { | |
| 687 MixinApplicationElement aMixinApplication = aClass; | |
| 688 aMixinTypes.add(aMixinApplication.mixinType); | |
| 689 aClass = aMixinApplication.superclass; | |
| 690 } | |
| 691 KClass bClass = b; | |
| 692 while (bClass != null) { | |
| 693 InterfaceType mixinType = testing.getMixinTypeForClass(bClass); | |
| 694 if (mixinType == null) break; | |
| 695 bMixinTypes.add(mixinType); | |
| 696 bClass = testing.getSuperclassForClass(bClass); | |
| 697 } | |
| 698 if (aMixinTypes.isNotEmpty || aMixinTypes.isNotEmpty) { | |
| 699 Pair<ClassEntity, ClassEntity> pair = | |
| 700 new Pair<ClassEntity, ClassEntity>(aClass, bClass); | |
| 701 if (assumedMixinApplications.contains(pair)) { | |
| 702 return true; | |
| 703 } else { | |
| 704 assumedMixinApplications.add(pair); | |
| 705 bool result = strategy.testTypeLists( | |
| 706 a, b, 'mixinTypes', aMixinTypes, bMixinTypes); | |
| 707 assumedMixinApplications.remove(pair); | |
| 708 return result; | |
| 709 } | |
| 710 } | |
| 711 } else { | |
| 712 if (testing.isUnnamedMixinApplication(b)) { | |
| 713 return false; | |
| 714 } | |
| 715 } | |
| 716 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 717 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 718 } | |
| 719 return false; | |
| 720 case ElementKind.LIBRARY: | |
| 721 if (b is KLibrary) { | |
| 722 LibraryElement libraryA = a; | |
| 723 return libraryA.canonicalUri == b.canonicalUri; | |
| 724 } | |
| 725 return false; | |
| 726 case ElementKind.FUNCTION: | |
| 727 if (b is KMethod) { | |
| 728 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 729 strategy.testElements( | |
| 730 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass) && | |
| 731 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 732 } else if (b is KLocalFunction) { | |
| 733 LocalFunctionElement aLocalFunction = a; | |
| 734 return strategy.test(a, b, 'name', a.name, b.name ?? '') && | |
| 735 strategy.testElements(a, b, 'executableContext', | |
| 736 aLocalFunction.executableContext, b.executableContext) && | |
| 737 strategy.testElements(a, b, 'memberContext', | |
| 738 aLocalFunction.memberContext, b.memberContext); | |
| 739 } | |
| 740 return false; | |
| 741 case ElementKind.GETTER: | |
| 742 if (b is KGetter) { | |
| 743 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 744 strategy.testElements( | |
| 745 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass) && | |
| 746 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 747 } | |
| 748 return false; | |
| 749 case ElementKind.SETTER: | |
| 750 if (b is KSetter) { | |
| 751 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 752 strategy.testElements( | |
| 753 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass) && | |
| 754 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 755 } | |
| 756 return false; | |
| 757 case ElementKind.FIELD: | |
| 758 if (b is KField) { | |
| 759 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 760 strategy.testElements( | |
| 761 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass) && | |
| 762 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 763 } | |
| 764 return false; | |
| 765 case ElementKind.TYPE_VARIABLE: | |
| 766 if (b is KTypeVariable) { | |
| 767 TypeVariableElement aElement = a; | |
| 768 return strategy.test(a, b, 'index', aElement.index, b.index) && | |
| 769 strategy.testElements(a, b, 'typeDeclaration', | |
| 770 aElement.typeDeclaration, b.typeDeclaration); | |
| 771 } | |
| 772 return false; | |
| 773 default: | |
| 774 throw new UnsupportedError('Unsupported equivalence: ' | |
| 775 '$a (${a.runtimeType}) vs $b (${b.runtimeType})'); | |
| 776 } | |
| 777 } | |
| 778 | |
| 779 bool typeEquivalence(ResolutionDartType a, DartType b, | |
| 780 {TestStrategy strategy}) { | |
| 781 if (identical(a, b)) return true; | |
| 782 if (a == null || b == null) return false; | |
| 783 strategy ??= defaultStrategy; | |
| 784 switch (a.kind) { | |
| 785 case ResolutionTypeKind.DYNAMIC: | |
| 786 return b is DynamicType; | |
| 787 case ResolutionTypeKind.VOID: | |
| 788 return b is VoidType; | |
| 789 case ResolutionTypeKind.INTERFACE: | |
| 790 if (b is InterfaceType) { | |
| 791 ResolutionInterfaceType aType = a; | |
| 792 return strategy.testElements(a, b, 'element', a.element, b.element) && | |
| 793 strategy.testTypeLists( | |
| 794 a, b, 'typeArguments', aType.typeArguments, b.typeArguments); | |
| 795 } | |
| 796 return false; | |
| 797 case ResolutionTypeKind.TYPE_VARIABLE: | |
| 798 if (b is TypeVariableType) { | |
| 799 return strategy.testElements(a, b, 'element', a.element, b.element); | |
| 800 } | |
| 801 return false; | |
| 802 case ResolutionTypeKind.FUNCTION: | |
| 803 if (b is FunctionType) { | |
| 804 ResolutionFunctionType aType = a; | |
| 805 return strategy.testTypes( | |
| 806 a, b, 'returnType', aType.returnType, b.returnType) && | |
| 807 strategy.testTypeLists(a, b, 'parameterTypes', | |
| 808 aType.parameterTypes, b.parameterTypes) && | |
| 809 strategy.testTypeLists(a, b, 'optionalParameterTypes', | |
| 810 aType.optionalParameterTypes, b.optionalParameterTypes) && | |
| 811 strategy.testLists(a, b, 'namedParameters', aType.namedParameters, | |
| 812 b.namedParameters) && | |
| 813 strategy.testTypeLists(a, b, 'namedParameterTypes', | |
| 814 aType.namedParameterTypes, b.namedParameterTypes); | |
| 815 } | |
| 816 return false; | |
| 817 default: | |
| 818 throw new UnsupportedError('Unsupported equivalence: ' | |
| 819 '$a (${a.runtimeType}) vs $b (${b.runtimeType})'); | |
| 820 } | |
| 821 } | |
| 822 | |
| 823 bool constantEquivalence(ConstantExpression exp1, ConstantExpression exp2, | |
| 824 {TestStrategy strategy}) { | |
| 825 strategy ??= defaultStrategy; | |
| 826 return areConstantsEquivalent(exp1, exp2, strategy: strategy); | |
| 827 } | |
| 828 | |
| 829 bool constantValueEquivalence(ConstantValue value1, ConstantValue value2, | |
| 830 {TestStrategy strategy}) { | |
| 831 strategy ??= defaultStrategy; | |
| 832 return areConstantValuesEquivalent(value1, value2, strategy: strategy); | |
| 833 } | |
| 834 } | |
| OLD | NEW |